You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by el...@apache.org on 2016/03/07 19:28:20 UTC
[42/59] [partial] calcite git commit: [CALCITE-1078] Detach avatica
from the core calcite Maven project
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java b/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
new file mode 100644
index 0000000..a5a3852
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ConnectionProperty.Type;
+import org.apache.calcite.avatica.util.Casing;
+import org.apache.calcite.avatica.util.Quoting;
+
+import java.util.Map;
+
+/**
+ * Definitions of properties that drive the behavior of
+ * {@link org.apache.calcite.avatica.AvaticaDatabaseMetaData}.
+ */
+public enum InternalProperty {
+ /** Whether identifiers are matched case-sensitively. */
+ CASE_SENSITIVE(Type.BOOLEAN, true),
+
+ /** Character that quotes identifiers. */
+ SQL_KEYWORDS(Type.STRING, null),
+
+ /** How identifiers are quoted. */
+ QUOTING(Quoting.class, Quoting.DOUBLE_QUOTE),
+
+ /** How identifiers are stored if they are quoted. */
+ QUOTED_CASING(Casing.class, Casing.UNCHANGED),
+
+ /** How identifiers are stored if they are not quoted. */
+ UNQUOTED_CASING(Casing.class, Casing.TO_UPPER),
+
+ /** How identifiers are stored if they are not quoted. */
+ NULL_SORTING(NullSorting.class, NullSorting.END);
+
+ private final Type type;
+ private final Class enumClass;
+ private final Object defaultValue;
+
+ /** Creates an InternalProperty based on an enum. */
+ <E extends Enum> InternalProperty(Class<E> enumClass, E defaultValue) {
+ this(Type.ENUM, enumClass, defaultValue);
+ }
+
+ /** Creates an InternalProperty based on a non-enum type. */
+ InternalProperty(Type type, Object defaultValue) {
+ this(type, null, defaultValue);
+ }
+
+ private InternalProperty(Type type, Class enumClass, Object defaultValue) {
+ this.type = type;
+ this.enumClass = enumClass;
+ this.defaultValue = defaultValue;
+ }
+
+ private <T> T get_(Map<InternalProperty, Object> map, T defaultValue) {
+ final Object s = map.get(this);
+ if (s != null) {
+ return (T) s;
+ }
+ if (defaultValue != null) {
+ return (T) defaultValue;
+ }
+ throw new RuntimeException("Required property '" + name()
+ + "' not specified");
+ }
+
+ /** Returns the string value of this property, or null if not specified and
+ * no default. */
+ public String getString(Map<InternalProperty, Object> map) {
+ assert type == Type.STRING;
+ return get_(map, (String) defaultValue);
+ }
+
+ /** Returns the boolean value of this property. Throws if not set and no
+ * default. */
+ public boolean getBoolean(Map<InternalProperty, Object> map) {
+ assert type == Type.BOOLEAN;
+ return get_(map, (Boolean) defaultValue);
+ }
+
+ /** Returns the enum value of this property. Throws if not set and no
+ * default. */
+ public <E extends Enum> E getEnum(Map<InternalProperty, Object> map,
+ Class<E> enumClass) {
+ assert type == Type.ENUM;
+ //noinspection unchecked
+ return get_(map, (E) defaultValue);
+ }
+
+ /** Where nulls appear in a sorted relation. */
+ enum NullSorting {
+ START, END, LOW, HIGH,
+ }
+}
+
+// End InternalProperty.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java b/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
new file mode 100644
index 0000000..4cc460c
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -0,0 +1,1272 @@
+/*
+ * 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.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.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.
+ *
+ * <p>Also holds other abstract methods that are not related to metadata
+ * that each provider must implement. This is not ideal.</p>
+ */
+public interface Meta {
+
+ /**
+ * Returns a map of static database properties.
+ *
+ * <p>The provider can omit properties whose value is the same as the
+ * default.
+ */
+ Map<DatabaseProperty, Object> getDatabaseProperties(ConnectionHandle ch);
+
+ /** Per {@link DatabaseMetaData#getTables(String, String, String, String[])}. */
+ MetaResultSet getTables(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern,
+ List<String> 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.
+ *
+ * <p>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<Object> createIterable(StatementHandle stmt, QueryState state, Signature signature,
+ List<TypedValue> 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.
+ *
+ * <p>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.
+ *
+ * <p>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 Execute result
+ */
+ ExecuteResult execute(StatementHandle h, List<TypedValue> 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<String, String> 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.
+ *
+ * <p>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}.</p>
+ */
+ ConnectionProperties connectionSync(ConnectionHandle ch, ConnectionProperties connProps);
+
+ /** Factory to create instances of {@link Meta}. */
+ interface Factory {
+ Meta create(List<String> 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.
+ *
+ * <p>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;
+
+ <T> 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> T getProp(Meta meta, ConnectionHandle ch, Class<T> 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> T getProp(Map<DatabaseProperty, Object> map, Class<T> 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.
+ *
+ * <p>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<MetaResultSet> resultSets;
+
+ public ExecuteResult(List<MetaResultSet> resultSets) {
+ this.resultSets = resultSets;
+ }
+ }
+
+ /** Meta data from which a result set can be constructed.
+ *
+ * <p>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 {
+ private static final FieldDescriptor CLASS_NAME_DESCRIPTOR = Common.CursorFactory.
+ getDescriptor().findFieldByNumber(Common.CursorFactory.CLASS_NAME_FIELD_NUMBER);
+
+ public final Style style;
+ public final Class clazz;
+ @JsonIgnore
+ public final List<Field> fields;
+ public final List<String> fieldNames;
+
+ private CursorFactory(Style style, Class clazz, List<Field> fields,
+ List<String> 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<String> 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<Field> fields,
+ List<String> 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<String> fieldNames) {
+ return new CursorFactory(Style.MAP, null, null, fieldNames);
+ }
+
+ public static CursorFactory deduce(List<ColumnMetaData> 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;
+
+ if (proto.hasField(CLASS_NAME_DESCRIPTOR)) {
+ 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() {
+ return Objects.hash(clazz, fieldNames, fields, style);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o == this
+ || o instanceof CursorFactory
+ && Objects.equals(clazz, ((CursorFactory) o).clazz)
+ && Objects.equals(fieldNames, ((CursorFactory) o).fieldNames)
+ && Objects.equals(fields, ((CursorFactory) o).fields)
+ && style == ((CursorFactory) o).style;
+ }
+ }
+
+ /** 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 {
+ private static final FieldDescriptor SQL_DESCRIPTOR = Common.Signature
+ .getDescriptor().findFieldByNumber(Common.Signature.SQL_FIELD_NUMBER);
+ private static final FieldDescriptor CURSOR_FACTORY_DESCRIPTOR = Common.Signature
+ .getDescriptor().findFieldByNumber(Common.Signature.CURSOR_FACTORY_FIELD_NUMBER);
+
+ public final List<ColumnMetaData> columns;
+ public final String sql;
+ public final List<AvaticaParameter> parameters;
+ public final transient Map<String, Object> internalParameters;
+ public final CursorFactory cursorFactory;
+
+ public final Meta.StatementType statementType;
+
+ /** Creates a Signature. */
+ public Signature(List<ColumnMetaData> columns,
+ String sql,
+ List<AvaticaParameter> parameters,
+ Map<String, Object> 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<ColumnMetaData> columns,
+ @JsonProperty("sql") String sql,
+ @JsonProperty("parameters") List<AvaticaParameter> parameters,
+ @JsonProperty("cursorFactory") CursorFactory cursorFactory,
+ @JsonProperty("statementType") Meta.StatementType statementType) {
+ return new Signature(columns, sql, parameters,
+ Collections.<String, Object>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 <E> List<E> sanitize(List<E> list) {
+ return list == null ? Collections.<E>emptyList() : list;
+ }
+
+ private <K, V> Map<K, V> sanitize(Map<K, V> map) {
+ return map == null ? Collections.<K, V>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<ColumnMetaData> metadata = new ArrayList<>(protoSignature.getColumnsCount());
+ for (Common.ColumnMetaData protoMetadata : protoSignature.getColumnsList()) {
+ metadata.add(ColumnMetaData.fromProto(protoMetadata));
+ }
+
+ List<AvaticaParameter> parameters = new ArrayList<>(protoSignature.getParametersCount());
+ for (Common.AvaticaParameter protoParam : protoSignature.getParametersList()) {
+ parameters.add(AvaticaParameter.fromProto(protoParam));
+ }
+
+ String sql = null;
+ if (protoSignature.hasField(SQL_DESCRIPTOR)) {
+ sql = protoSignature.getSql();
+ }
+
+ CursorFactory cursorFactory = null;
+ if (protoSignature.hasField(CURSOR_FACTORY_DESCRIPTOR)) {
+ 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() {
+ return Objects.hash(columns, cursorFactory, parameters, sql);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o == this
+ || o instanceof Signature
+ && Objects.equals(columns, ((Signature) o).columns)
+ && Objects.equals(cursorFactory, ((Signature) o).cursorFactory)
+ && Objects.equals(parameters, ((Signature) o).parameters)
+ && Objects.equals(sql, ((Signature) o).sql);
+ }
+ }
+
+ /** A collection of rows. */
+ class Frame {
+ private static final FieldDescriptor HAS_ARRAY_VALUE_DESCRIPTOR = Common.ColumnValue
+ .getDescriptor().findFieldByNumber(Common.ColumnValue.HAS_ARRAY_VALUE_FIELD_NUMBER);
+ private static final FieldDescriptor SCALAR_VALUE_DESCRIPTOR = Common.ColumnValue
+ .getDescriptor().findFieldByNumber(Common.ColumnValue.SCALAR_VALUE_FIELD_NUMBER);
+ /** 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<Object> rows;
+
+ public Frame(long offset, boolean done, Iterable<Object> 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<Object> 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(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<Object> parsedRows = new ArrayList<>(proto.getRowsCount());
+ for (Common.Row protoRow : proto.getRowsList()) {
+ ArrayList<Object> 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) {
+ return column.hasField(HAS_ARRAY_VALUE_DESCRIPTOR)
+ || column.hasField(SCALAR_VALUE_DESCRIPTOR);
+ }
+
+ /**
+ * 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<Object> 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 (!column.hasField(SCALAR_VALUE_DESCRIPTOR)) {
+ // Array
+ List<Object> 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 = column.hasField(SCALAR_VALUE_DESCRIPTOR);
+ 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 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() {
+ return Objects.hash(done, offset, rows);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o == this
+ || o instanceof Frame
+ && equalRows(rows, ((Frame) o).rows)
+ && offset == ((Frame) o).offset
+ && done == ((Frame) o).done;
+ }
+
+ private static boolean equalRows(Iterable<Object> rows, Iterable<Object> otherRows) {
+ if (null == rows) {
+ if (null != otherRows) {
+ return false;
+ }
+ } else {
+ Iterator<Object> iter1 = rows.iterator();
+ Iterator<Object> iter2 = otherRows.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<Object> obj2List = (List<Object>) obj2;
+ if (!Arrays.equals((Object[]) obj1, obj2List.toArray())) {
+ 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<Object> obj1List = (List<Object>) obj1;
+ if (!Arrays.equals(obj1List.toArray(), (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 true;
+ }
+ }
+
+ /** 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 {
+ private static final FieldDescriptor SIGNATURE_DESCRIPTOR = Common.StatementHandle
+ .getDescriptor().findFieldByNumber(Common.StatementHandle.SIGNATURE_FIELD_NUMBER);
+ 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() {
+ Common.StatementHandle.Builder builder = Common.StatementHandle.newBuilder()
+ .setConnectionId(connectionId).setId(id);
+ if (null != signature) {
+ builder.setSignature(signature.toProto());
+ }
+ return builder.build();
+ }
+
+ public static StatementHandle fromProto(Common.StatementHandle protoHandle) {
+ // Signature is optional in the update path for executes.
+ Signature signature = null;
+ if (protoHandle.hasField(SIGNATURE_DESCRIPTOR)) {
+ signature = Signature.fromProto(protoHandle.getSignature());
+ }
+ return new StatementHandle(protoHandle.getConnectionId(), protoHandle.getId(), signature);
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(connectionId, id, signature);
+ }
+
+ @Override public boolean equals(Object o) {
+ return o == this
+ || o instanceof StatementHandle
+ && Objects.equals(connectionId, ((StatementHandle) o).connectionId)
+ && Objects.equals(signature, ((StatementHandle) o).signature)
+ && id == ((StatementHandle) o).id;
+ }
+ }
+
+ /** A pojo containing various client-settable {@link java.sql.Connection} properties.
+ *
+ * <p>{@code java.lang} types are used here so that {@code null} can be used to indicate
+ * a value has no been set.</p>
+ *
+ * <p>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}.</p>
+ */
+ @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
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
new file mode 100644
index 0000000..7bb99fc
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
@@ -0,0 +1,961 @@
+/*
+ * 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.remote.TypedValue;
+import org.apache.calcite.avatica.util.ArrayIteratorCursor;
+import org.apache.calcite.avatica.util.Cursor;
+import org.apache.calcite.avatica.util.IteratorCursor;
+import org.apache.calcite.avatica.util.ListIteratorCursor;
+import org.apache.calcite.avatica.util.MapIteratorCursor;
+import org.apache.calcite.avatica.util.RecordIteratorCursor;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Basic implementation of {@link Meta}.
+ *
+ * <p>Each sub-class must implement the two remaining abstract methods,
+ * {@link #prepare} and
+ * {@link #prepareAndExecute}.
+ * It should also override metadata methods such as {@link #getCatalogs(Meta.ConnectionHandle)} and
+ * {@link #getTables} for the element types for which it has instances; the
+ * default metadata methods return empty collections.
+ */
+public abstract class MetaImpl implements Meta {
+ /** The {@link AvaticaConnection} backing {@code this}. */
+ protected final AvaticaConnection connection;
+ /** Represents the various states specific to {@link #connection}.
+ *
+ * <p>Note: this instance is used recursively with {@link #connection}'s getter and setter
+ * methods.</p>
+ */
+ protected final ConnectionPropertiesImpl connProps;
+
+ public MetaImpl(AvaticaConnection connection) {
+ this.connection = connection;
+ this.connProps = new ConnectionPropertiesImpl();
+ }
+
+ /** Uses a {@link org.apache.calcite.avatica.Meta.CursorFactory} to convert
+ * an {@link Iterable} into a
+ * {@link org.apache.calcite.avatica.util.Cursor}. */
+ public static Cursor createCursor(CursorFactory cursorFactory,
+ Iterable<Object> iterable) {
+ switch (cursorFactory.style) {
+ case OBJECT:
+ return new IteratorCursor<Object>(iterable.iterator()) {
+ protected Getter createGetter(int ordinal) {
+ return new ObjectGetter(ordinal);
+ }
+ };
+ case ARRAY:
+ @SuppressWarnings("unchecked") final Iterable<Object[]> iterable1 =
+ (Iterable<Object[]>) (Iterable) iterable;
+ return new ArrayIteratorCursor(iterable1.iterator());
+ case RECORD:
+ @SuppressWarnings("unchecked") final Class<Object> clazz =
+ cursorFactory.clazz;
+ return new RecordIteratorCursor<Object>(iterable.iterator(), clazz);
+ case RECORD_PROJECTION:
+ @SuppressWarnings("unchecked") final Class<Object> clazz2 =
+ cursorFactory.clazz;
+ return new RecordIteratorCursor<Object>(iterable.iterator(), clazz2,
+ cursorFactory.fields);
+ case LIST:
+ @SuppressWarnings("unchecked") final Iterable<List<Object>> iterable2 =
+ (Iterable<List<Object>>) (Iterable) iterable;
+ return new ListIteratorCursor(iterable2.iterator());
+ case MAP:
+ @SuppressWarnings("unchecked") final Iterable<Map<String, Object>>
+ iterable3 =
+ (Iterable<Map<String, Object>>) (Iterable) iterable;
+ return new MapIteratorCursor(iterable3.iterator(),
+ cursorFactory.fieldNames);
+ default:
+ throw new AssertionError("unknown style: " + cursorFactory.style);
+ }
+ }
+
+ public static List<List<Object>> collect(CursorFactory cursorFactory,
+ final Iterator<Object> iterator, List<List<Object>> list) {
+ final Iterable<Object> iterable = new Iterable<Object>() {
+ public Iterator<Object> iterator() {
+ return iterator;
+ }
+ };
+ return collect(cursorFactory, iterable, list);
+ }
+
+ public static List<List<Object>> collect(CursorFactory cursorFactory,
+ Iterable<Object> iterable, final List<List<Object>> list) {
+ switch (cursorFactory.style) {
+ case OBJECT:
+ for (Object o : iterable) {
+ list.add(Collections.singletonList(o));
+ }
+ return list;
+ case ARRAY:
+ @SuppressWarnings("unchecked") final Iterable<Object[]> iterable1 =
+ (Iterable<Object[]>) (Iterable) iterable;
+ for (Object[] objects : iterable1) {
+ list.add(Arrays.asList(objects));
+ }
+ return list;
+ case RECORD:
+ case RECORD_PROJECTION:
+ final Field[] fields;
+ switch (cursorFactory.style) {
+ case RECORD:
+ fields = cursorFactory.clazz.getFields();
+ break;
+ default:
+ fields = cursorFactory.fields.toArray(
+ new Field[cursorFactory.fields.size()]);
+ }
+ for (Object o : iterable) {
+ final Object[] objects = new Object[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ try {
+ objects[i] = field.get(o);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ list.add(Arrays.asList(objects));
+ }
+ return list;
+ case LIST:
+ @SuppressWarnings("unchecked") final Iterable<List<Object>> iterable2 =
+ (Iterable<List<Object>>) (Iterable) iterable;
+ for (List<Object> objects : iterable2) {
+ list.add(objects);
+ }
+ return list;
+ case MAP:
+ @SuppressWarnings("unchecked") final Iterable<Map<String, Object>>
+ iterable3 =
+ (Iterable<Map<String, Object>>) (Iterable) iterable;
+ for (Map<String, Object> map : iterable3) {
+ final List<Object> objects = new ArrayList<Object>();
+ for (String fieldName : cursorFactory.fieldNames) {
+ objects.add(map.get(fieldName));
+ }
+ list.add(objects);
+ }
+ return list;
+ default:
+ throw new AssertionError("unknown style: " + cursorFactory.style);
+ }
+ }
+
+ @Override public void openConnection(ConnectionHandle ch, Map<String, String> info) {
+ // dummy implementation, connection is already created at this point
+ }
+
+ @Override public void closeConnection(ConnectionHandle ch) {
+ // TODO: implement
+ //
+ // lots of Calcite tests break with this simple implementation,
+ // requires investigation
+
+// try {
+// connection.close();
+// } catch (SQLException e) {
+// throw new RuntimeException(e);
+// }
+ }
+
+ @Override public ConnectionProperties connectionSync(ConnectionHandle ch,
+ ConnectionProperties connProps) {
+ this.connProps.merge(connProps);
+ this.connProps.setDirty(false);
+ return this.connProps;
+ }
+
+ public StatementHandle createStatement(ConnectionHandle ch) {
+ return new StatementHandle(ch.id, connection.statementCount++, null);
+ }
+
+ /** Creates an empty result set. Useful for JDBC metadata methods that are
+ * not implemented or which query entities that are not supported (e.g.
+ * triggers in Lingual). */
+ protected <E> MetaResultSet createEmptyResultSet(final Class<E> clazz) {
+ return createResultSet(Collections.<String, Object>emptyMap(),
+ fieldMetaData(clazz).columns,
+ CursorFactory.deduce(fieldMetaData(clazz).columns, null),
+ Frame.EMPTY);
+ }
+
+ public static ColumnMetaData columnMetaData(String name, int index,
+ Class<?> type) {
+ TypeInfo pair = TypeInfo.m.get(type);
+ ColumnMetaData.Rep rep =
+ ColumnMetaData.Rep.VALUE_MAP.get(type);
+ ColumnMetaData.AvaticaType scalarType =
+ ColumnMetaData.scalar(pair.sqlType, pair.sqlTypeName, rep);
+ return new ColumnMetaData(
+ index, false, true, false, false,
+ pair.primitive
+ ? DatabaseMetaData.columnNullable
+ : DatabaseMetaData.columnNoNulls,
+ true, -1, name, name, null,
+ 0, 0, null, null, scalarType, true, false, false,
+ scalarType.columnClassName());
+ }
+
+ protected static ColumnMetaData.StructType fieldMetaData(Class clazz) {
+ final List<ColumnMetaData> list = new ArrayList<ColumnMetaData>();
+ for (Field field : clazz.getFields()) {
+ if (Modifier.isPublic(field.getModifiers())
+ && !Modifier.isStatic(field.getModifiers())) {
+ list.add(
+ columnMetaData(
+ AvaticaUtils.camelToUpper(field.getName()),
+ list.size() + 1, field.getType()));
+ }
+ }
+ return ColumnMetaData.struct(list);
+ }
+
+ protected MetaResultSet createResultSet(
+ Map<String, Object> internalParameters, List<ColumnMetaData> columns,
+ CursorFactory cursorFactory, Frame firstFrame) {
+ try {
+ final AvaticaStatement statement = connection.createStatement();
+ final Signature signature =
+ new Signature(columns, "", Collections.<AvaticaParameter>emptyList(),
+ internalParameters, cursorFactory, Meta.StatementType.SELECT);
+ return MetaResultSet.create(connection.id, statement.getId(), true,
+ signature, firstFrame);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** An object that has a name. */
+ public interface Named {
+ @JsonIgnore String getName();
+ }
+
+ /** Metadata describing a column. */
+ public static class MetaColumn implements Named {
+ public final String tableCat;
+ public final String tableSchem;
+ public final String tableName;
+ public final String columnName;
+ public final int dataType;
+ public final String typeName;
+ public final int columnSize;
+ public final String bufferLength = null;
+ public final Integer decimalDigits;
+ public final int numPrecRadix;
+ public final int nullable;
+ public final String remarks = null;
+ public final String columnDef = null;
+ public final String sqlDataType = null;
+ public final String sqlDatetimeSub = null;
+ public final int charOctetLength;
+ public final int ordinalPosition;
+ public final String isNullable;
+ public final String scopeCatalog = null;
+ public final String scopeSchema = null;
+ public final String scopeTable = null;
+ public final String sourceDataType = null;
+ public final String isAutoincrement = null;
+ public final String isGeneratedcolumn = null;
+
+ public MetaColumn(
+ String tableCat,
+ String tableSchem,
+ String tableName,
+ String columnName,
+ int dataType,
+ String typeName,
+ int columnSize,
+ Integer decimalDigits,
+ int numPrecRadix,
+ int nullable,
+ int charOctetLength,
+ int ordinalPosition,
+ String isNullable) {
+ this.tableCat = tableCat;
+ this.tableSchem = tableSchem;
+ this.tableName = tableName;
+ this.columnName = columnName;
+ this.dataType = dataType;
+ this.typeName = typeName;
+ this.columnSize = columnSize;
+ this.decimalDigits = decimalDigits;
+ this.numPrecRadix = numPrecRadix;
+ this.nullable = nullable;
+ this.charOctetLength = charOctetLength;
+ this.ordinalPosition = ordinalPosition;
+ this.isNullable = isNullable;
+ }
+
+ public String getName() {
+ return columnName;
+ }
+ }
+
+ /** Metadata describing a TypeInfo. */
+
+ /** Metadata describing a table. */
+ public static class MetaTable implements Named {
+ public final String tableCat;
+ public final String tableSchem;
+ public final String tableName;
+ public final String tableType;
+ public final String remarks = null;
+ public final String typeCat = null;
+ public final String typeSchem = null;
+ public final String typeName = null;
+ public final String selfReferencingColName = null;
+ public final String refGeneration = null;
+
+ public MetaTable(String tableCat,
+ String tableSchem,
+ String tableName,
+ String tableType) {
+ this.tableCat = tableCat;
+ this.tableSchem = tableSchem;
+ this.tableName = tableName;
+ this.tableType = tableType;
+ }
+
+ public String getName() {
+ return tableName;
+ }
+ }
+
+ /** Metadata describing a schema. */
+ public static class MetaSchema implements Named {
+ public final String tableCatalog;
+ public final String tableSchem;
+
+ public MetaSchema(
+ String tableCatalog,
+ String tableSchem) {
+ this.tableCatalog = tableCatalog;
+ this.tableSchem = tableSchem;
+ }
+
+ public String getName() {
+ return tableSchem;
+ }
+ }
+
+ /** Metadata describing a catalog. */
+ public static class MetaCatalog implements Named {
+ public final String tableCat;
+
+ public MetaCatalog(
+ String tableCatalog) {
+ this.tableCat = tableCatalog;
+ }
+
+ public String getName() {
+ return tableCat;
+ }
+ }
+
+ /** Metadata describing a table type. */
+ public static class MetaTableType {
+ public final String tableType;
+
+ public MetaTableType(String tableType) {
+ this.tableType = tableType;
+ }
+ }
+
+ /** Metadata describing a procedure. */
+ public static class MetaProcedure {
+ }
+
+ /** Metadata describing a procedure column. */
+ public static class MetaProcedureColumn {
+ }
+
+ /** Metadata describing a column privilege. */
+ public static class MetaColumnPrivilege {
+ }
+
+ /** Metadata describing a table privilege. */
+ public static class MetaTablePrivilege {
+ }
+
+ /** Metadata describing the best identifier for a row. */
+ public static class MetaBestRowIdentifier {
+ }
+
+ /** Metadata describing a version column. */
+ public static class MetaVersionColumn {
+ public final short scope;
+ public final String columnName;
+ public final int dataType;
+ public final String typeName;
+ public final int columnSize;
+ public final int bufferLength;
+ public final short decimalDigits;
+ public final short pseudoColumn;
+
+ MetaVersionColumn(short scope, String columnName, int dataType,
+ String typeName, int columnSize, int bufferLength, short decimalDigits,
+ short pseudoColumn) {
+ this.scope = scope;
+ this.columnName = columnName;
+ this.dataType = dataType;
+ this.typeName = typeName;
+ this.columnSize = columnSize;
+ this.bufferLength = bufferLength;
+ this.decimalDigits = decimalDigits;
+ this.pseudoColumn = pseudoColumn;
+ }
+ }
+
+ /** Metadata describing a primary key. */
+ public static class MetaPrimaryKey {
+ public final String tableCat;
+ public final String tableSchem;
+ public final String tableName;
+ public final String columnName;
+ public final short keySeq;
+ public final String pkName;
+
+ MetaPrimaryKey(String tableCat, String tableSchem, String tableName,
+ String columnName, short keySeq, String pkName) {
+ this.tableCat = tableCat;
+ this.tableSchem = tableSchem;
+ this.tableName = tableName;
+ this.columnName = columnName;
+ this.keySeq = keySeq;
+ this.pkName = pkName;
+ }
+ }
+
+ /** Metadata describing an imported key. */
+ public static class MetaImportedKey {
+ }
+
+ /** Metadata describing an exported key. */
+ public static class MetaExportedKey {
+ }
+
+ /** Metadata describing a cross reference. */
+ public static class MetaCrossReference {
+ }
+
+ /** Metadata describing type info. */
+ public static class MetaTypeInfo implements Named {
+ public final String typeName;
+ public final int dataType;
+ public final int precision;
+ public final String literalPrefix;
+ public final String literalSuffix;
+ //TODO: Add create parameter for type on DDL
+ public final String createParams = null;
+ public final int nullable;
+ public final boolean caseSensitive;
+ public final int searchable;
+ public final boolean unsignedAttribute;
+ public final boolean fixedPrecScale;
+ public final boolean autoIncrement;
+ public final String localTypeName;
+ public final int minimumScale;
+ public final int maximumScale;
+ public final int sqlDataType = 0;
+ public final int sqlDatetimeSub = 0;
+ public final Integer numPrecRadix; //nullable int
+
+ public MetaTypeInfo(
+ String typeName,
+ int dataType,
+ int precision,
+ String literalPrefix,
+ String literalSuffix,
+ int nullable,
+ boolean caseSensitive,
+ int searchable,
+ boolean unsignedAttribute,
+ boolean fixedPrecScale,
+ boolean autoIncrement,
+ int minimumScale,
+ int maximumScale,
+ int numPrecRadix) {
+ this.typeName = typeName;
+ this.dataType = dataType;
+ this.precision = precision;
+ this.literalPrefix = literalPrefix;
+ this.literalSuffix = literalSuffix;
+ this.nullable = nullable;
+ this.caseSensitive = caseSensitive;
+ this.searchable = searchable;
+ this.unsignedAttribute = unsignedAttribute;
+ this.fixedPrecScale = fixedPrecScale;
+ this.autoIncrement = autoIncrement;
+ this.localTypeName = typeName;
+ // Make min to be 0 instead of -1
+ this.minimumScale = minimumScale == -1 ? 0 : minimumScale;
+ this.maximumScale = maximumScale == -1 ? 0 : maximumScale;
+ this.numPrecRadix = numPrecRadix == 0 ? null : numPrecRadix;
+ }
+
+ public String getName() {
+ return typeName;
+ }
+ }
+
+ /** Metadata describing index info. */
+ public static class MetaIndexInfo {
+ }
+
+ /** Metadata describing a user-defined type. */
+ public static class MetaUdt {
+ }
+
+ /** Metadata describing a super-type. */
+ public static class MetaSuperType {
+ }
+
+ /** Metadata describing an attribute. */
+ public static class MetaAttribute {
+ }
+
+ /** Metadata describing a client info property. */
+ public static class MetaClientInfoProperty {
+ }
+
+ /** Metadata describing a function. */
+ public static class MetaFunction {
+ }
+
+ /** Metadata describing a function column. */
+ public static class MetaFunctionColumn {
+ }
+
+ /** Metadata describing a pseudo column. */
+ public static class MetaPseudoColumn {
+ }
+
+ /** Metadata describing a super-table. */
+ public static class MetaSuperTable {
+ }
+
+ public Map<DatabaseProperty, Object> getDatabaseProperties(ConnectionHandle ch) {
+ return Collections.emptyMap();
+ }
+
+ public MetaResultSet getTables(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern,
+ List<String> typeList) {
+ return createEmptyResultSet(MetaTable.class);
+ }
+
+ public MetaResultSet getColumns(ConnectionHandle ch, String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern,
+ Pat columnNamePattern) {
+ return createEmptyResultSet(MetaColumn.class);
+ }
+
+ public MetaResultSet getSchemas(ConnectionHandle ch, String catalog, Pat schemaPattern) {
+ return createEmptyResultSet(MetaSchema.class);
+ }
+
+ public MetaResultSet getCatalogs(ConnectionHandle ch) {
+ return createEmptyResultSet(MetaCatalog.class);
+ }
+
+ public MetaResultSet getTableTypes(ConnectionHandle ch) {
+ return createEmptyResultSet(MetaTableType.class);
+ }
+
+ public MetaResultSet getProcedures(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat procedureNamePattern) {
+ return createEmptyResultSet(MetaProcedure.class);
+ }
+
+ public MetaResultSet getProcedureColumns(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat procedureNamePattern,
+ Pat columnNamePattern) {
+ return createEmptyResultSet(MetaProcedureColumn.class);
+ }
+
+ public MetaResultSet getColumnPrivileges(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table,
+ Pat columnNamePattern) {
+ return createEmptyResultSet(MetaColumnPrivilege.class);
+ }
+
+ public MetaResultSet getTablePrivileges(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern) {
+ return createEmptyResultSet(MetaTablePrivilege.class);
+ }
+
+ public MetaResultSet getBestRowIdentifier(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table,
+ int scope,
+ boolean nullable) {
+ return createEmptyResultSet(MetaBestRowIdentifier.class);
+ }
+
+ public MetaResultSet getVersionColumns(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table) {
+ return createEmptyResultSet(MetaVersionColumn.class);
+ }
+
+ public MetaResultSet getPrimaryKeys(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table) {
+ return createEmptyResultSet(MetaPrimaryKey.class);
+ }
+
+ public MetaResultSet getImportedKeys(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table) {
+ return createEmptyResultSet(MetaImportedKey.class);
+ }
+
+ public MetaResultSet getExportedKeys(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table) {
+ return createEmptyResultSet(MetaExportedKey.class);
+ }
+
+ public MetaResultSet getCrossReference(ConnectionHandle ch,
+ String parentCatalog,
+ String parentSchema,
+ String parentTable,
+ String foreignCatalog,
+ String foreignSchema,
+ String foreignTable) {
+ return createEmptyResultSet(MetaCrossReference.class);
+ }
+
+ public MetaResultSet getTypeInfo(ConnectionHandle ch) {
+ return createEmptyResultSet(MetaTypeInfo.class);
+ }
+
+ public MetaResultSet getIndexInfo(ConnectionHandle ch,
+ String catalog,
+ String schema,
+ String table,
+ boolean unique,
+ boolean approximate) {
+ return createEmptyResultSet(MetaIndexInfo.class);
+ }
+
+ public MetaResultSet getUDTs(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat typeNamePattern,
+ int[] types) {
+ return createEmptyResultSet(MetaUdt.class);
+ }
+
+ public MetaResultSet getSuperTypes(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat typeNamePattern) {
+ return createEmptyResultSet(MetaSuperType.class);
+ }
+
+ public MetaResultSet getSuperTables(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern) {
+ return createEmptyResultSet(MetaSuperTable.class);
+ }
+
+ public MetaResultSet getAttributes(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat typeNamePattern,
+ Pat attributeNamePattern) {
+ return createEmptyResultSet(MetaAttribute.class);
+ }
+
+ public MetaResultSet getClientInfoProperties(ConnectionHandle ch) {
+ return createEmptyResultSet(MetaClientInfoProperty.class);
+ }
+
+ public MetaResultSet getFunctions(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat functionNamePattern) {
+ return createEmptyResultSet(MetaFunction.class);
+ }
+
+ public MetaResultSet getFunctionColumns(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat functionNamePattern,
+ Pat columnNamePattern) {
+ return createEmptyResultSet(MetaFunctionColumn.class);
+ }
+
+ public MetaResultSet getPseudoColumns(ConnectionHandle ch,
+ String catalog,
+ Pat schemaPattern,
+ Pat tableNamePattern,
+ Pat columnNamePattern) {
+ return createEmptyResultSet(MetaPseudoColumn.class);
+ }
+
+ @Override public Iterable<Object> createIterable(StatementHandle handle, QueryState state,
+ Signature signature, List<TypedValue> parameterValues, Frame firstFrame) {
+ if (firstFrame != null && firstFrame.done) {
+ return firstFrame.rows;
+ }
+ AvaticaStatement stmt;
+ try {
+ stmt = connection.lookupStatement(handle);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ return new FetchIterable(stmt, state,
+ firstFrame, parameterValues);
+ }
+
+ public Frame fetch(AvaticaStatement stmt, List<TypedValue> parameterValues,
+ long offset, int fetchMaxRowCount) throws NoSuchStatementException, MissingResultsException {
+ return null;
+ }
+
+ /** Information about a type. */
+ private static class TypeInfo {
+ private static Map<Class<?>, TypeInfo> m =
+ new HashMap<Class<?>, TypeInfo>();
+ static {
+ put(boolean.class, true, Types.BOOLEAN, "BOOLEAN");
+ put(Boolean.class, false, Types.BOOLEAN, "BOOLEAN");
+ put(byte.class, true, Types.TINYINT, "TINYINT");
+ put(Byte.class, false, Types.TINYINT, "TINYINT");
+ put(short.class, true, Types.SMALLINT, "SMALLINT");
+ put(Short.class, false, Types.SMALLINT, "SMALLINT");
+ put(int.class, true, Types.INTEGER, "INTEGER");
+ put(Integer.class, false, Types.INTEGER, "INTEGER");
+ put(long.class, true, Types.BIGINT, "BIGINT");
+ put(Long.class, false, Types.BIGINT, "BIGINT");
+ put(float.class, true, Types.FLOAT, "FLOAT");
+ put(Float.class, false, Types.FLOAT, "FLOAT");
+ put(double.class, true, Types.DOUBLE, "DOUBLE");
+ put(Double.class, false, Types.DOUBLE, "DOUBLE");
+ put(String.class, false, Types.VARCHAR, "VARCHAR");
+ put(java.sql.Date.class, false, Types.DATE, "DATE");
+ put(Time.class, false, Types.TIME, "TIME");
+ put(Timestamp.class, false, Types.TIMESTAMP, "TIMESTAMP");
+ }
+
+ private final boolean primitive;
+ private final int sqlType;
+ private final String sqlTypeName;
+
+ public TypeInfo(boolean primitive, int sqlType, String sqlTypeName) {
+ this.primitive = primitive;
+ this.sqlType = sqlType;
+ this.sqlTypeName = sqlTypeName;
+ }
+
+ static void put(Class clazz, boolean primitive, int sqlType,
+ String sqlTypeName) {
+ m.put(clazz, new TypeInfo(primitive, sqlType, sqlTypeName));
+ }
+ }
+
+ /** Iterator that never returns any elements. */
+ private static class EmptyIterator implements Iterator<Object> {
+ public static final Iterator<Object> INSTANCE = new EmptyIterator();
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasNext() {
+ return false;
+ }
+
+ public Object next() {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /** Iterable that yields an iterator over rows coming from a sequence of
+ * {@link Meta.Frame}s. */
+ private class FetchIterable implements Iterable<Object> {
+ private final AvaticaStatement stmt;
+ private final QueryState state;
+ private final Frame firstFrame;
+ private final List<TypedValue> parameterValues;
+
+ public FetchIterable(AvaticaStatement stmt, QueryState state, Frame firstFrame,
+ List<TypedValue> parameterValues) {
+ this.stmt = stmt;
+ this.state = state;
+ this.firstFrame = firstFrame;
+ this.parameterValues = parameterValues;
+ }
+
+ public Iterator<Object> iterator() {
+ return new FetchIterator(stmt, state, firstFrame, parameterValues);
+ }
+ }
+
+ /** Iterator over rows coming from a sequence of {@link Meta.Frame}s. */
+ private class FetchIterator implements Iterator<Object> {
+ private final AvaticaStatement stmt;
+ private final QueryState state;
+ private Frame frame;
+ private Iterator<Object> rows;
+ private List<TypedValue> parameterValues;
+ private List<TypedValue> originalParameterValues;
+ private long currentOffset = 0;
+
+ public FetchIterator(AvaticaStatement stmt, QueryState state, Frame firstFrame,
+ List<TypedValue> parameterValues) {
+ this.stmt = stmt;
+ this.state = state;
+ this.parameterValues = parameterValues;
+ this.originalParameterValues = parameterValues;
+ if (firstFrame == null) {
+ frame = Frame.MORE;
+ rows = EmptyIterator.INSTANCE;
+ } else {
+ frame = firstFrame;
+ rows = firstFrame.rows.iterator();
+ }
+ moveNext();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+
+ public boolean hasNext() {
+ return rows != null;
+ }
+
+ public Object next() {
+ if (rows == null) {
+ throw new NoSuchElementException();
+ }
+ final Object o = rows.next();
+ currentOffset++;
+ moveNext();
+ return o;
+ }
+
+ private void moveNext() {
+ for (;;) {
+ if (rows.hasNext()) {
+ break;
+ }
+ if (frame.done) {
+ rows = null;
+ break;
+ }
+ try {
+ // currentOffset updated after element is read from `rows` iterator
+ frame = fetch(stmt.handle, currentOffset, AvaticaStatement.DEFAULT_FETCH_SIZE);
+ } catch (NoSuchStatementException e) {
+ resetStatement();
+ // re-fetch the batch where we left off
+ continue;
+ } catch (MissingResultsException e) {
+ try {
+ // We saw the statement, but it didnt' have a resultset initialized. So, reset it.
+ if (!stmt.syncResults(state, currentOffset)) {
+ // This returned false, so there aren't actually any more results to iterate over
+ frame = null;
+ rows = null;
+ break;
+ }
+ // syncResults returning true means we need to fetch those results
+ } catch (NoSuchStatementException e1) {
+ // Tried to reset the result set, but lost the statement, save a loop before retrying.
+ resetStatement();
+ // Will just loop back around to a MissingResultsException, but w/e recursion
+ }
+ // Kick back to the top to try to fetch again (in both branches)
+ continue;
+ }
+ parameterValues = null; // don't execute next time
+ if (frame == null) {
+ rows = null;
+ break;
+ }
+ // It is valid for rows to be empty, so we go around the loop again to
+ // check
+ rows = frame.rows.iterator();
+ }
+ }
+
+ private void resetStatement() {
+ // If we have to reset the statement, we need to reset the parameterValues too
+ parameterValues = originalParameterValues;
+ // Defer to the statement to reset itself
+ stmt.resetStatement();
+ }
+ }
+
+ /** Returns whether a list of parameter values has any null elements. */
+ public static boolean checkParameterValueHasNull(List<TypedValue> parameterValues) {
+ for (TypedValue x : parameterValues) {
+ if (x == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+// End MetaImpl.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java
new file mode 100644
index 0000000..7746769
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java
@@ -0,0 +1,41 @@
+/*
+ * 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.Meta.StatementHandle;
+
+import java.sql.ResultSet;
+
+/**
+ * An Exception which denotes that a cached Statement is present but has no {@link ResultSet}.
+ */
+public class MissingResultsException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final StatementHandle handle;
+
+ public MissingResultsException(StatementHandle handle) {
+ this.handle = handle;
+ }
+
+ public StatementHandle getHandle() {
+ return handle;
+ }
+}
+
+// End MissingResultsException.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
new file mode 100644
index 0000000..b5a940d
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * An Exception that denotes that the given Connection is not cached.
+ */
+public class NoSuchConnectionException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String connectionId;
+
+ public NoSuchConnectionException(String connectionId) {
+ this.connectionId = connectionId;
+ }
+
+ public String getConnectionId() {
+ return connectionId;
+ }
+}
+
+// End NoSuchConnectionException.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
new file mode 100644
index 0000000..321011b
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.Meta.StatementHandle;
+
+/**
+ * An Exception that denotes that the given Statement is not cached.
+ */
+public class NoSuchStatementException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final StatementHandle stmtHandle;
+
+ public NoSuchStatementException(StatementHandle stmtHandle) {
+ this.stmtHandle = stmtHandle;
+ }
+
+ public StatementHandle getStatementHandle() {
+ return stmtHandle;
+ }
+}
+
+// End NoSuchStatementException.java