/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.avatica;
import org.apache.calcite.avatica.proto.Common;
import org.apache.calcite.avatica.remote.ProtobufService;
import org.apache.calcite.avatica.remote.TypedValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
/**
* Command handler for getting various metadata. Should be implemented by each
* driver.
*
* Also holds other abstract methods that are not related to metadata
* that each provider must implement. This is not ideal.
*/
public interface Meta {
/**
* Returns a map of static database properties.
*
* The provider can omit properties whose value is the same as the
* default.
*/
Map getDatabaseProperties(ConnectionHandle ch);
/** Per {@link DatabaseMetaData#getTables(String, String, String, String[])}. */
MetaResultSet getTables(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat tableNamePattern,
List typeList);
/** Per {@link DatabaseMetaData#getColumns(String, String, String, String)}. */
MetaResultSet getColumns(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat tableNamePattern,
Pat columnNamePattern);
MetaResultSet getSchemas(ConnectionHandle ch, String catalog, Pat schemaPattern);
/** Per {@link DatabaseMetaData#getCatalogs()}. */
MetaResultSet getCatalogs(ConnectionHandle ch);
/** Per {@link DatabaseMetaData#getTableTypes()}. */
MetaResultSet getTableTypes(ConnectionHandle ch);
/** Per {@link DatabaseMetaData#getProcedures(String, String, String)}. */
MetaResultSet getProcedures(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat procedureNamePattern);
/** Per {@link DatabaseMetaData#getProcedureColumns(String, String, String, String)}. */
MetaResultSet getProcedureColumns(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat procedureNamePattern,
Pat columnNamePattern);
/** Per {@link DatabaseMetaData#getColumnPrivileges(String, String, String, String)}. */
MetaResultSet getColumnPrivileges(ConnectionHandle ch,
String catalog,
String schema,
String table,
Pat columnNamePattern);
/** Per {@link DatabaseMetaData#getTablePrivileges(String, String, String)}. */
MetaResultSet getTablePrivileges(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat tableNamePattern);
/** Per
* {@link DatabaseMetaData#getBestRowIdentifier(String, String, String, int, boolean)}. */
MetaResultSet getBestRowIdentifier(ConnectionHandle ch,
String catalog,
String schema,
String table,
int scope,
boolean nullable);
/** Per {@link DatabaseMetaData#getVersionColumns(String, String, String)}. */
MetaResultSet getVersionColumns(ConnectionHandle ch, String catalog, String schema, String table);
/** Per {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. */
MetaResultSet getPrimaryKeys(ConnectionHandle ch, String catalog, String schema, String table);
/** Per {@link DatabaseMetaData#getImportedKeys(String, String, String)}. */
MetaResultSet getImportedKeys(ConnectionHandle ch, String catalog, String schema, String table);
/** Per {@link DatabaseMetaData#getExportedKeys(String, String, String)}. */
MetaResultSet getExportedKeys(ConnectionHandle ch, String catalog, String schema, String table);
/** Per
* {@link DatabaseMetaData#getCrossReference(String, String, String, String, String, String)}. */
MetaResultSet getCrossReference(ConnectionHandle ch,
String parentCatalog,
String parentSchema,
String parentTable,
String foreignCatalog,
String foreignSchema,
String foreignTable);
/** Per {@link DatabaseMetaData#getTypeInfo()}. */
MetaResultSet getTypeInfo(ConnectionHandle ch);
/** Per {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. */
MetaResultSet getIndexInfo(ConnectionHandle ch, String catalog,
String schema,
String table,
boolean unique,
boolean approximate);
/** Per {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. */
MetaResultSet getUDTs(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat typeNamePattern,
int[] types);
/** Per {@link DatabaseMetaData#getSuperTypes(String, String, String)}. */
MetaResultSet getSuperTypes(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat typeNamePattern);
/** Per {@link DatabaseMetaData#getSuperTables(String, String, String)}. */
MetaResultSet getSuperTables(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat tableNamePattern);
/** Per {@link DatabaseMetaData#getAttributes(String, String, String, String)}. */
MetaResultSet getAttributes(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat typeNamePattern,
Pat attributeNamePattern);
/** Per {@link DatabaseMetaData#getClientInfoProperties()}. */
MetaResultSet getClientInfoProperties(ConnectionHandle ch);
/** Per {@link DatabaseMetaData#getFunctions(String, String, String)}. */
MetaResultSet getFunctions(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat functionNamePattern);
/** Per {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. */
MetaResultSet getFunctionColumns(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat functionNamePattern,
Pat columnNamePattern);
/** Per {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}. */
MetaResultSet getPseudoColumns(ConnectionHandle ch,
String catalog,
Pat schemaPattern,
Pat tableNamePattern,
Pat columnNamePattern);
/** Creates an iterable for a result set.
*
* The default implementation just returns {@code iterable}, which it
* requires to be not null; derived classes may instead choose to execute the
* relational expression in {@code signature}. */
Iterable createIterable(StatementHandle stmt, QueryState state, Signature signature,
List parameterValues, Frame firstFrame);
/** Prepares a statement.
*
* @param ch Connection handle
* @param sql SQL query
* @param maxRowCount Negative for no limit (different meaning than JDBC)
* @return Signature of prepared statement
*/
StatementHandle prepare(ConnectionHandle ch, String sql, long maxRowCount);
/** Prepares and executes a statement.
*
* @param h Statement handle
* @param sql SQL query
* @param maxRowCount Negative for no limit (different meaning than JDBC)
* @param callback Callback to lock, clear and assign cursor
*
* @return Result containing statement ID, and if a query, a result set and
* first frame of data
*/
ExecuteResult prepareAndExecute(StatementHandle h, String sql,
long maxRowCount, PrepareCallback callback) throws NoSuchStatementException;
/** Returns a frame of rows.
*
* The frame describes whether there may be another frame. If there is not
* another frame, the current iteration is done when we have finished the
* rows in the this frame.
*
*
The default implementation always returns null.
*
* @param h Statement handle
* @param offset Zero-based offset of first row in the requested frame
* @param fetchMaxRowCount Maximum number of rows to return; negative means
* no limit
* @return Frame, or null if there are no more
*/
Frame fetch(StatementHandle h, long offset, int fetchMaxRowCount) throws
NoSuchStatementException, MissingResultsException;
/** Executes a prepared statement.
*
* @param h Statement handle
* @param parameterValues A list of parameter values; may be empty, not null
* @param maxRowCount Maximum number of rows to return; negative means
* no limit
* @return Frame, or null if there are no more
*/
ExecuteResult execute(StatementHandle h, List parameterValues,
long maxRowCount) throws NoSuchStatementException;
/** Called during the creation of a statement to allocate a new handle.
*
* @param ch Connection handle
*/
StatementHandle createStatement(ConnectionHandle ch);
/** Closes a statement. */
void closeStatement(StatementHandle h);
/**
* Opens (creates) a connection. The client allocates its own connection ID which the server is
* then made aware of through the {@link ConnectionHandle}. The Map {@code info} argument is
* analogous to the {@link Properties} typically passed to a "normal" JDBC Driver. Avatica
* specific properties should not be included -- only properties for the underlying driver.
*
* @param ch A ConnectionHandle encapsulates information about the connection to be opened
* as provided by the client.
* @param info A Map corresponding to the Properties typically passed to a JDBC Driver.
*/
void openConnection(ConnectionHandle ch, Map info);
/** Closes a connection */
void closeConnection(ConnectionHandle ch);
/**
* Re-set the {@link ResultSet} on a Statement. Not a JDBC method.
* @return True if there are results to fetch after resetting to the given offset. False otherwise
*/
boolean syncResults(StatementHandle sh, QueryState state, long offset)
throws NoSuchStatementException;
/**
* Makes all changes since the last commit/rollback permanent. Analogy to
* {@link Connection#commit()}.
*
* @param ch A reference to the real JDBC Connection.
*/
void commit(ConnectionHandle ch);
/**
* Undoes all changes since the last commit/rollback. Analogy to
* {@link Connection#rollback()};
*
* @param ch A reference to the real JDBC Connection.
*/
void rollback(ConnectionHandle ch);
/** Sync client and server view of connection properties.
*
* Note: this interface is considered "experimental" and may undergo further changes as this
* functionality is extended to other aspects of state management for
* {@link java.sql.Connection}, {@link java.sql.Statement}, and {@link java.sql.ResultSet}.
*/
ConnectionProperties connectionSync(ConnectionHandle ch, ConnectionProperties connProps);
/** Factory to create instances of {@link Meta}. */
interface Factory {
Meta create(List args);
}
/** Wrapper to remind API calls that a parameter is a pattern (allows '%' and
* '_' wildcards, per the JDBC spec) rather than a string to be matched
* exactly. */
class Pat {
public final String s;
private Pat(String s) {
this.s = s;
}
@Override public String toString() {
return "Pat[" + s + "]";
}
@JsonCreator
public static Pat of(@JsonProperty("s") String name) {
return new Pat(name);
}
}
/** Database property.
*
* Values exist for methods, such as
* {@link DatabaseMetaData#getSQLKeywords()}, which always return the same
* value at all times and across connections.
*
* @see #getDatabaseProperties(Meta.ConnectionHandle)
*/
enum DatabaseProperty {
/** Database property containing the value of
* {@link DatabaseMetaData#getNumericFunctions()}. */
GET_NUMERIC_FUNCTIONS(""),
/** Database property containing the value of
* {@link DatabaseMetaData#getStringFunctions()}. */
GET_STRING_FUNCTIONS(""),
/** Database property containing the value of
* {@link DatabaseMetaData#getSystemFunctions()}. */
GET_SYSTEM_FUNCTIONS(""),
/** Database property containing the value of
* {@link DatabaseMetaData#getTimeDateFunctions()}. */
GET_TIME_DATE_FUNCTIONS(""),
/** Database property containing the value of
* {@link DatabaseMetaData#getSQLKeywords()}. */
GET_S_Q_L_KEYWORDS(""),
/** Database property containing the value of
* {@link DatabaseMetaData#getDefaultTransactionIsolation()}. */
GET_DEFAULT_TRANSACTION_ISOLATION(Connection.TRANSACTION_NONE);
public final Class> type;
public final Object defaultValue;
public final Method method;
DatabaseProperty(T defaultValue) {
this.defaultValue = defaultValue;
final String methodName = AvaticaUtils.toCamelCase(name());
try {
this.method = DatabaseMetaData.class.getMethod(methodName);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
this.type = AvaticaUtils.box(method.getReturnType());
assert defaultValue == null || defaultValue.getClass() == type;
}
/** Returns a value of this property, using the default value if the map
* does not contain an explicit value. */
public T getProp(Meta meta, ConnectionHandle ch, Class aClass) {
return getProp(meta.getDatabaseProperties(ch), aClass);
}
/** Returns a value of this property, using the default value if the map
* does not contain an explicit value. */
public T getProp(Map map, Class aClass) {
assert aClass == type;
Object v = map.get(this);
if (v == null) {
v = defaultValue;
}
return aClass.cast(v);
}
public static DatabaseProperty fromProto(Common.DatabaseProperty proto) {
return DatabaseProperty.valueOf(proto.getName());
}
public Common.DatabaseProperty toProto() {
return Common.DatabaseProperty.newBuilder().setName(name()).build();
}
}
/** Response from execute.
*
* Typically a query will have a result set and rowCount = -1;
* a DML statement will have a rowCount and no result sets.
*/
class ExecuteResult {
public final List resultSets;
public ExecuteResult(List resultSets) {
this.resultSets = resultSets;
}
}
/** Meta data from which a result set can be constructed.
*
* If {@code updateCount} is not -1, the result is just a count. A result
* set cannot be constructed. */
class MetaResultSet {
public final String connectionId;
public final int statementId;
public final boolean ownStatement;
public final Frame firstFrame;
public final Signature signature;
public final long updateCount;
@Deprecated // to be removed before 2.0
protected MetaResultSet(String connectionId, int statementId,
boolean ownStatement, Signature signature, Frame firstFrame,
int updateCount) {
this(connectionId, statementId, ownStatement, signature, firstFrame,
(long) updateCount);
}
protected MetaResultSet(String connectionId, int statementId,
boolean ownStatement, Signature signature, Frame firstFrame,
long updateCount) {
this.signature = signature;
this.connectionId = connectionId;
this.statementId = statementId;
this.ownStatement = ownStatement;
this.firstFrame = firstFrame; // may be null even if signature is not null
this.updateCount = updateCount;
}
public static MetaResultSet create(String connectionId, int statementId,
boolean ownStatement, Signature signature, Frame firstFrame) {
return new MetaResultSet(connectionId, statementId, ownStatement,
Objects.requireNonNull(signature), firstFrame, -1L);
}
public static MetaResultSet count(String connectionId, int statementId,
long updateCount) {
assert updateCount >= 0
: "Meta.count(" + connectionId + ", " + statementId + ", "
+ updateCount + ")";
return new MetaResultSet(connectionId, statementId, false, null, null,
updateCount);
}
}
/** Information necessary to convert an {@link Iterable} into a
* {@link org.apache.calcite.avatica.util.Cursor}. */
final class CursorFactory {
public final Style style;
public final Class clazz;
@JsonIgnore
public final List fields;
public final List fieldNames;
private CursorFactory(Style style, Class clazz, List fields,
List fieldNames) {
assert (fieldNames != null)
== (style == Style.RECORD_PROJECTION || style == Style.MAP);
assert (fields != null) == (style == Style.RECORD_PROJECTION);
this.style = Objects.requireNonNull(style);
this.clazz = clazz;
this.fields = fields;
this.fieldNames = fieldNames;
}
@JsonCreator
public static CursorFactory create(@JsonProperty("style") Style style,
@JsonProperty("clazz") Class clazz,
@JsonProperty("fieldNames") List fieldNames) {
switch (style) {
case OBJECT:
return OBJECT;
case ARRAY:
return ARRAY;
case LIST:
return LIST;
case RECORD:
return record(clazz);
case RECORD_PROJECTION:
return record(clazz, null, fieldNames);
case MAP:
return map(fieldNames);
default:
throw new AssertionError("unknown style: " + style);
}
}
public static final CursorFactory OBJECT =
new CursorFactory(Style.OBJECT, null, null, null);
public static final CursorFactory ARRAY =
new CursorFactory(Style.ARRAY, null, null, null);
public static final CursorFactory LIST =
new CursorFactory(Style.LIST, null, null, null);
public static CursorFactory record(Class resultClazz) {
return new CursorFactory(Style.RECORD, resultClazz, null, null);
}
public static CursorFactory record(Class resultClass, List fields,
List fieldNames) {
if (fields == null) {
fields = new ArrayList<>();
for (String fieldName : fieldNames) {
try {
fields.add(resultClass.getField(fieldName));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}
return new CursorFactory(Style.RECORD_PROJECTION, resultClass, fields,
fieldNames);
}
public static CursorFactory map(List fieldNames) {
return new CursorFactory(Style.MAP, null, null, fieldNames);
}
public static CursorFactory deduce(List columns,
Class resultClazz) {
if (columns.size() == 1) {
return OBJECT;
}
if (resultClazz == null) {
return ARRAY;
}
if (resultClazz.isArray()) {
return ARRAY;
}
if (List.class.isAssignableFrom(resultClazz)) {
return LIST;
}
return record(resultClazz);
}
public Common.CursorFactory toProto() {
Common.CursorFactory.Builder builder = Common.CursorFactory.newBuilder();
if (null != clazz) {
builder.setClassName(clazz.getName());
}
builder.setStyle(style.toProto());
if (null != fieldNames) {
builder.addAllFieldNames(fieldNames);
}
return builder.build();
}
public static CursorFactory fromProto(Common.CursorFactory proto) {
// Reconstruct CursorFactory
Class> clz = null;
FieldDescriptor clzFieldDesc = proto.getDescriptorForType()
.findFieldByNumber(Common.CursorFactory.CLASS_NAME_FIELD_NUMBER);
if (proto.hasField(clzFieldDesc)) {
try {
clz = Class.forName(proto.getClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return CursorFactory.create(Style.fromProto(proto.getStyle()), clz,
proto.getFieldNamesList());
}
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
result = prime * result + ((fieldNames == null) ? 0 : fieldNames.hashCode());
result = prime * result + ((fields == null) ? 0 : fields.hashCode());
result = prime * result + ((style == null) ? 0 : style.hashCode());
return result;
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof CursorFactory) {
CursorFactory other = (CursorFactory) o;
if (null == clazz) {
if (null != other.clazz) {
return false;
}
} else if (!clazz.equals(other.clazz)) {
return false;
}
if (null == fieldNames) {
if (null != other.fieldNames) {
return false;
}
} else if (!fieldNames.equals(other.fieldNames)) {
return false;
}
if (null == fields) {
if (null != other.fields) {
return false;
}
} else if (!fields.equals(other.fields)) {
return false;
}
return style == other.style;
}
return false;
}
}
/** How logical fields are represented in the objects returned by the
* iterator. */
enum Style {
OBJECT,
RECORD,
RECORD_PROJECTION,
ARRAY,
LIST,
MAP;
public Common.CursorFactory.Style toProto() {
return Common.CursorFactory.Style.valueOf(name());
}
public static Style fromProto(Common.CursorFactory.Style proto) {
return Style.valueOf(proto.name());
}
}
/** Result of preparing a statement. */
public class Signature {
public final List columns;
public final String sql;
public final List parameters;
public final transient Map internalParameters;
public final CursorFactory cursorFactory;
public final Meta.StatementType statementType;
/** Creates a Signature. */
public Signature(List columns,
String sql,
List parameters,
Map internalParameters,
CursorFactory cursorFactory,
Meta.StatementType statementType) {
this.columns = columns;
this.sql = sql;
this.parameters = parameters;
this.internalParameters = internalParameters;
this.cursorFactory = cursorFactory;
this.statementType = statementType;
}
/** Used by Jackson to create a Signature by de-serializing JSON. */
@JsonCreator
public static Signature create(
@JsonProperty("columns") List columns,
@JsonProperty("sql") String sql,
@JsonProperty("parameters") List parameters,
@JsonProperty("cursorFactory") CursorFactory cursorFactory,
@JsonProperty("statementType") Meta.StatementType statementType) {
return new Signature(columns, sql, parameters,
Collections.emptyMap(), cursorFactory, statementType);
}
/** Returns a copy of this Signature, substituting given CursorFactory. */
public Signature setCursorFactory(CursorFactory cursorFactory) {
return new Signature(columns, sql, parameters, internalParameters,
cursorFactory, statementType);
}
/** Creates a copy of this Signature with null lists and maps converted to
* empty. */
public Signature sanitize() {
if (columns == null || parameters == null || internalParameters == null
|| statementType == null) {
return new Signature(sanitize(columns), sql, sanitize(parameters),
sanitize(internalParameters), cursorFactory,
Meta.StatementType.SELECT);
}
return this;
}
private List sanitize(List list) {
return list == null ? Collections.emptyList() : list;
}
private Map sanitize(Map map) {
return map == null ? Collections.emptyMap() : map;
}
public Common.Signature toProto() {
Common.Signature.Builder builder = Common.Signature.newBuilder();
if (null != sql) {
builder.setSql(sql);
}
if (null != cursorFactory) {
builder.setCursorFactory(cursorFactory.toProto());
}
if (null != columns) {
for (ColumnMetaData column : columns) {
builder.addColumns(column.toProto());
}
}
if (null != parameters) {
for (AvaticaParameter parameter : parameters) {
builder.addParameters(parameter.toProto());
}
}
return builder.build();
}
public static Signature fromProto(Common.Signature protoSignature) {
List metadata = new ArrayList<>(protoSignature.getColumnsCount());
for (Common.ColumnMetaData protoMetadata : protoSignature.getColumnsList()) {
metadata.add(ColumnMetaData.fromProto(protoMetadata));
}
List parameters = new ArrayList<>(protoSignature.getParametersCount());
for (Common.AvaticaParameter protoParam : protoSignature.getParametersList()) {
parameters.add(AvaticaParameter.fromProto(protoParam));
}
final Descriptor desc = protoSignature.getDescriptorForType();
String sql = null;
if (ProtobufService.hasField(protoSignature, desc, Common.Signature.SQL_FIELD_NUMBER)) {
sql = protoSignature.getSql();
}
CursorFactory cursorFactory = null;
if (ProtobufService.hasField(protoSignature, desc,
Common.Signature.CURSOR_FACTORY_FIELD_NUMBER)) {
cursorFactory = CursorFactory.fromProto(protoSignature.getCursorFactory());
}
final Meta.StatementType statementType =
Meta.StatementType.fromProto(protoSignature.getStatementType());
return Signature.create(metadata, sql, parameters, cursorFactory, statementType);
}
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((columns == null) ? 0 : columns.hashCode());
result = prime * result + ((cursorFactory == null) ? 0 : cursorFactory.hashCode());
result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
result = prime * result + ((sql == null) ? 0 : sql.hashCode());
return result;
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof Signature) {
Signature other = (Signature) o;
if (null == columns) {
if (null != other.columns) {
return false;
}
} else if (!columns.equals(other.columns)) {
return false;
}
if (null == cursorFactory) {
if (null != other.cursorFactory) {
return false;
}
} else if (!cursorFactory.equals(other.cursorFactory)) {
return false;
}
if (null == parameters) {
if (null != other.parameters) {
return false;
}
} else if (!parameters.equals(other.parameters)) {
return false;
}
if (null == sql) {
if (null != other.sql) {
return false;
}
} else if (!sql.equals(other.sql)) {
return false;
}
return true;
}
return false;
}
}
/** A collection of rows. */
public class Frame {
/** Frame that has zero rows and is the last frame. */
public static final Frame EMPTY =
new Frame(0, true, Collections.emptyList());
/** Frame that has zero rows but may have another frame. */
public static final Frame MORE =
new Frame(0, false, Collections.emptyList());
/** Zero-based offset of first row. */
public final long offset;
/** Whether this is definitely the last frame of rows.
* If true, there are no more rows.
* If false, there may or may not be more rows. */
public final boolean done;
/** The rows. */
public final Iterable rows;
public Frame(long offset, boolean done, Iterable rows) {
this.offset = offset;
this.done = done;
this.rows = rows;
}
@JsonCreator
public static Frame create(@JsonProperty("offset") int offset,
@JsonProperty("done") boolean done,
@JsonProperty("rows") List rows) {
if (offset == 0 && done && rows.isEmpty()) {
return EMPTY;
}
return new Frame(offset, done, rows);
}
public Common.Frame toProto() {
Common.Frame.Builder builder = Common.Frame.newBuilder();
builder.setDone(done).setOffset(offset);
for (Object row : this.rows) {
if (null == row) {
// Does this need to be persisted for some reason?
continue;
}
if (row instanceof Object[]) {
final Common.Row.Builder rowBuilder = Common.Row.newBuilder();
for (Object element : (Object[]) row) {
final Common.ColumnValue.Builder columnBuilder = Common.ColumnValue.newBuilder();
if (element instanceof List) {
columnBuilder.setHasArrayValue(true);
List> list = (List>) element;
// Add each element in the list/array to the column's value
for (Object listItem : list) {
columnBuilder.addArrayValue(serializeScalar(listItem));
}
} else {
// The default value, but still explicit.
columnBuilder.setHasArrayValue(false);
// Only one value for this column, a scalar.
columnBuilder.setScalarValue(serializeScalar(element));
}
// Add value to row
rowBuilder.addValue(columnBuilder.build());
}
// Collect all rows
builder.addRows(rowBuilder.build());
} else {
// Can a "row" be a primitive? A struct? Only an Array?
throw new RuntimeException("Only arrays are supported");
}
}
return builder.build();
}
static Common.TypedValue serializeScalar(Object element) {
final Common.TypedValue.Builder valueBuilder = Common.TypedValue.newBuilder();
// Numbers
if (element instanceof Byte) {
valueBuilder.setType(Common.Rep.BYTE).setNumberValue(((Byte) element).longValue());
} else if (element instanceof Short) {
valueBuilder.setType(Common.Rep.SHORT).setNumberValue(((Short) element).longValue());
} else if (element instanceof Integer) {
valueBuilder.setType(Common.Rep.INTEGER)
.setNumberValue(((Integer) element).longValue());
} else if (element instanceof Long) {
valueBuilder.setType(Common.Rep.LONG).setNumberValue((Long) element);
} else if (element instanceof Double) {
valueBuilder.setType(Common.Rep.DOUBLE).setDoubleValue((Double) element);
} else if (element instanceof Float) {
valueBuilder.setType(Common.Rep.FLOAT).setNumberValue(((Float) element).longValue());
} else if (element instanceof BigDecimal) {
valueBuilder.setType(Common.Rep.NUMBER)
.setDoubleValue(((BigDecimal) element).doubleValue());
// Strings
} else if (element instanceof String) {
valueBuilder.setType(Common.Rep.STRING)
.setStringValue((String) element);
} else if (element instanceof Character) {
valueBuilder.setType(Common.Rep.CHARACTER)
.setStringValue(((Character) element).toString());
// Bytes
} else if (element instanceof byte[]) {
valueBuilder.setType(Common.Rep.BYTE_STRING)
.setBytesValues(ByteString.copyFrom((byte[]) element));
// Boolean
} else if (element instanceof Boolean) {
valueBuilder.setType(Common.Rep.BOOLEAN).setBoolValue((boolean) element);
} else if (null == element) {
valueBuilder.setType(Common.Rep.NULL);
// Unhandled
} else {
throw new RuntimeException("Unhandled type in Frame: " + element.getClass());
}
return valueBuilder.build();
}
public static Frame fromProto(Common.Frame proto) {
List parsedRows = new ArrayList<>(proto.getRowsCount());
for (Common.Row protoRow : proto.getRowsList()) {
ArrayList row = new ArrayList<>(protoRow.getValueCount());
for (Common.ColumnValue protoColumn : protoRow.getValueList()) {
final Object value;
if (!isNewStyleColumn(protoColumn)) {
// Backward compatibility
value = parseOldStyleColumn(protoColumn);
} else {
// Current style parsing (separate scalar and array values)
value = parseColumn(protoColumn);
}
row.add(value);
}
parsedRows.add(row);
}
return new Frame(proto.getOffset(), proto.getDone(), parsedRows);
}
/**
* Determines whether this message contains the new attributes in the
* message. We can't directly test for the negative because our
* {@code hasField} trick does not work on repeated fields.
*
* @param column The protobuf column object
* @return True if the message is the new style, false otherwise.
*/
static boolean isNewStyleColumn(Common.ColumnValue column) {
final Descriptor desc = column.getDescriptorForType();
return ProtobufService.hasField(column, desc, Common.ColumnValue.HAS_ARRAY_VALUE_FIELD_NUMBER)
|| ProtobufService.hasField(column, desc, Common.ColumnValue.SCALAR_VALUE_FIELD_NUMBER);
}
/**
* For Calcite 1.5, we made the mistake of using array length to determine when the value for a
* column is a scalar or an array. This method performs the old parsing for backwards
* compatibility.
*
* @param column The protobuf ColumnValue object
* @return The parsed value for this column
*/
static Object parseOldStyleColumn(Common.ColumnValue column) {
if (column.getValueCount() > 1) {
List array = new ArrayList<>(column.getValueCount());
for (Common.TypedValue columnValue : column.getValueList()) {
array.add(getScalarValue(columnValue));
}
return array;
} else {
return getScalarValue(column.getValue(0));
}
}
/**
* Parses the value for a ColumnValue using the separated array and scalar attributes.
*
* @param column The protobuf ColumnValue object
* @return The parse value for this column
*/
static Object parseColumn(Common.ColumnValue column) {
// Verify that we have one or the other (scalar or array)
validateColumnValue(column);
if (!ProtobufService.hasField(column, column.getDescriptorForType(),
Common.ColumnValue.SCALAR_VALUE_FIELD_NUMBER)) {
// Array
List array = new ArrayList<>(column.getArrayValueCount());
for (Common.TypedValue arrayValue : column.getArrayValueList()) {
array.add(getScalarValue(arrayValue));
}
return array;
} else {
// Scalar
return getScalarValue(column.getScalarValue());
}
}
/**
* Verifies that a ColumnValue has only a scalar or array value, not both and not neither.
*
* @param column The protobuf ColumnValue object
* @throws IllegalArgumentException When the above condition is not met
*/
static void validateColumnValue(Common.ColumnValue column) {
final boolean hasScalar = ProtobufService.hasField(column, column.getDescriptorForType(),
Common.ColumnValue.SCALAR_VALUE_FIELD_NUMBER);
final boolean hasArrayValue = column.getHasArrayValue();
// These should always be different
if (hasScalar == hasArrayValue) {
throw new IllegalArgumentException("A column must have a scalar or array value, not "
+ (hasScalar ? "both" : "neither"));
}
}
static Object getScalarValue(Common.TypedValue protoElement) {
// TODO Should these be primitives or Objects?
switch (protoElement.getType()) {
case BYTE:
return Long.valueOf(protoElement.getNumberValue()).byteValue();
case SHORT:
return Long.valueOf(protoElement.getNumberValue()).shortValue();
case INTEGER:
return Long.valueOf(protoElement.getNumberValue()).intValue();
case LONG:
return protoElement.getNumberValue();
case FLOAT:
return Long.valueOf(protoElement.getNumberValue()).floatValue();
case DOUBLE:
return Double.valueOf(protoElement.getDoubleValue());
case NUMBER:
// TODO more cases here to expand on? BigInteger?
return BigDecimal.valueOf(protoElement.getDoubleValue());
case STRING:
return protoElement.getStringValue();
case CHARACTER:
// A single character in the string
return protoElement.getStringValue().charAt(0);
case BYTE_STRING:
return protoElement.getBytesValues().toByteArray();
case BOOLEAN:
return protoElement.getBoolValue();
case NULL:
return null;
default:
throw new RuntimeException("Unhandled type: " + protoElement.getType());
}
}
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (done ? 1231 : 1237);
result = prime * result + (int) (offset ^ (offset >>> 32));
result = prime * result + ((rows == null) ? 0 : rows.hashCode());
return result;
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof Frame) {
Frame other = (Frame) o;
if (null == rows) {
if (null != other.rows) {
return false;
}
} else {
Iterator iter1 = rows.iterator();
Iterator iter2 = other.rows.iterator();
while (iter1.hasNext() && iter2.hasNext()) {
Object obj1 = iter1.next();
Object obj2 = iter2.next();
// Can't just call equals on an array
if (obj1 instanceof Object[]) {
if (obj2 instanceof Object[]) {
// Compare array and array
if (!Arrays.equals((Object[]) obj1, (Object[]) obj2)) {
return false;
}
} else if (obj2 instanceof List) {
// compare array and list
@SuppressWarnings("unchecked")
List obj2List = (List) obj2;
if (!Arrays.equals((Object[]) obj1, obj2List.toArray(new Object[0]))) {
return false;
}
} else {
// compare array and something that isn't an array will always fail
return false;
}
} else if (obj1 instanceof List) {
if (obj2 instanceof Object[]) {
// Compare list and array
@SuppressWarnings("unchecked")
List obj1List = (List) obj1;
if (!Arrays.equals(obj1List.toArray(new Object[0]), (Object[]) obj2)) {
return false;
}
} else if (!obj1.equals(obj2)) {
// compare list and something else, let it fall to equals()
return false;
}
} else if (!obj1.equals(obj2)) {
// Not an array, leave it to equals()
return false;
}
}
// More elements in one of the iterables
if (iter1.hasNext() || iter2.hasNext()) {
return false;
}
}
return offset == other.offset && done == other.done;
}
return false;
}
}
/** Connection handle. */
class ConnectionHandle {
public final String id;
@Override public String toString() {
return id;
}
@JsonCreator
public ConnectionHandle(@JsonProperty("id") String id) {
this.id = id;
}
}
/** Statement handle. */
class StatementHandle {
public final String connectionId;
public final int id;
// not final because LocalService#apply(PrepareRequest)
/** Only present for PreparedStatement handles, null otherwise. */
public Signature signature;
@Override public String toString() {
return connectionId + "::" + Integer.toString(id);
}
@JsonCreator
public StatementHandle(
@JsonProperty("connectionId") String connectionId,
@JsonProperty("id") int id,
@JsonProperty("signature") Signature signature) {
this.connectionId = connectionId;
this.id = id;
this.signature = signature;
}
public Common.StatementHandle toProto() {
return Common.StatementHandle.newBuilder().setConnectionId(connectionId)
.setId(id).setSignature(signature.toProto()).build();
}
public static StatementHandle fromProto(Common.StatementHandle protoHandle) {
return new StatementHandle(protoHandle.getConnectionId(), protoHandle.getId(),
Signature.fromProto(protoHandle.getSignature()));
}
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((connectionId == null) ? 0 : connectionId.hashCode());
result = prime * result + id;
result = prime * result + ((signature == null) ? 0 : signature.hashCode());
return result;
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof StatementHandle) {
StatementHandle other = (StatementHandle) o;
if (null == connectionId) {
if (null != other.connectionId) {
return false;
}
} else if (!connectionId.equals(other.connectionId)) {
return false;
}
if (null == signature) {
if (null != other.signature) {
return false;
}
} else if (!signature.equals(other.signature)) {
return false;
}
return id == other.id;
}
return false;
}
}
/** A pojo containing various client-settable {@link java.sql.Connection} properties.
*
* {@code java.lang} types are used here so that {@code null} can be used to indicate
* a value has no been set.
*
* Note: this interface is considered "experimental" and may undergo further changes as this
* functionality is extended to other aspects of state management for
* {@link java.sql.Connection}, {@link java.sql.Statement}, and {@link java.sql.ResultSet}.
*/
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "connProps",
defaultImpl = ConnectionPropertiesImpl.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConnectionPropertiesImpl.class, name = "connPropsImpl")
})
interface ConnectionProperties {
/** Overwrite fields in {@code this} with any non-null fields in {@code that}
*
* @return {@code this}
*/
ConnectionProperties merge(ConnectionProperties that);
/** @return {@code true} when no properies have been set, {@code false} otherwise. */
@JsonIgnore
boolean isEmpty();
/** Set {@code autoCommit} status.
*
* @return {@code this}
*/
ConnectionProperties setAutoCommit(boolean val);
Boolean isAutoCommit();
/** Set {@code readOnly} status.
*
* @return {@code this}
*/
ConnectionProperties setReadOnly(boolean val);
Boolean isReadOnly();
/** Set {@code transactionIsolation} status.
*
* @return {@code this}
*/
ConnectionProperties setTransactionIsolation(int val);
Integer getTransactionIsolation();
/** Set {@code catalog}.
*
* @return {@code this}
*/
ConnectionProperties setCatalog(String val);
String getCatalog();
/** Set {@code schema}.
*
* @return {@code this}
*/
ConnectionProperties setSchema(String val);
String getSchema();
Common.ConnectionProperties toProto();
}
/** API to put a result set into a statement, being careful to enforce
* thread-safety and not to overwrite existing open result sets. */
interface PrepareCallback {
Object getMonitor();
void clear() throws SQLException;
void assign(Signature signature, Frame firstFrame, long updateCount)
throws SQLException;
void execute() throws SQLException;
}
/** Type of statement. */
enum StatementType {
SELECT, INSERT, UPDATE, DELETE, UPSERT, MERGE, OTHER_DML, IS_DML,
CREATE, DROP, ALTER, OTHER_DDL, CALL;
public boolean canUpdate() {
switch(this) {
case INSERT:
return true;
case IS_DML:
return true;
default:
return false;
}
}
public Common.StatementType toProto() {
return Common.StatementType.valueOf(name());
}
public static StatementType fromProto(Common.StatementType proto) {
return StatementType.valueOf(proto.name());
}
}
}
// End Meta.java