All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.avatica.Meta Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * 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 com.hazelcast.org.apache.calcite.avatica;

import com.hazelcast.org.apache.calcite.avatica.proto.Common;
import com.hazelcast.org.apache.calcite.avatica.remote.TypedValue;
import com.hazelcast.org.apache.calcite.avatica.util.FilteredConstants;

import com.hazelcast.com.fasterxml.jackson.annotation.JsonCreator;
import com.hazelcast.com.fasterxml.jackson.annotation.JsonIgnore;
import com.hazelcast.com.fasterxml.jackson.annotation.JsonProperty;
import com.hazelcast.com.fasterxml.jackson.annotation.JsonSubTypes;
import com.hazelcast.com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.hazelcast.com.google.protobuf.Descriptors.FieldDescriptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
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 parameters, 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 * @deprecated See {@link #prepareAndExecute(StatementHandle, String, long, int, PrepareCallback)} */ @Deprecated // to be removed before 2.0 ExecuteResult prepareAndExecute(StatementHandle h, String sql, long maxRowCount, PrepareCallback callback) throws NoSuchStatementException; /** Prepares and executes a statement. * * @param h Statement handle * @param sql SQL query * @param maxRowCount Maximum number of rows for the entire query. Negative for no limit * (different meaning than JDBC). * @param maxRowsInFirstFrame Maximum number of rows for the first frame. This value should * always be less than or equal to {@code maxRowCount} as the number of results are guaranteed * to be restricted by {@code maxRowCount} and the underlying database. * @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, int maxRowsInFirstFrame, PrepareCallback callback) throws NoSuchStatementException; /** Prepares a statement and then executes a number of SQL commands in one pass. * * @param h Statement handle * @param sqlCommands SQL commands to run * @return An array of update counts containing one element for each command in the batch. */ ExecuteBatchResult prepareAndExecuteBatch(StatementHandle h, List sqlCommands) throws NoSuchStatementException; /** Executes a collection of bound parameter values on a prepared statement. * * @param h Statement handle * @param parameterValues A collection of list of typed values, one list per batch * @return An array of update counts containing one element for each command in the batch. */ ExecuteBatchResult executeBatch(StatementHandle h, List> parameterValues) 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 Execute result * @deprecated See {@link #execute(StatementHandle, List, int)} */ @Deprecated // to be removed before 2.0 ExecuteResult execute(StatementHandle h, List parameterValues, long maxRowCount) throws NoSuchStatementException; /** Executes a prepared statement. * * @param h Statement handle * @param parameterValues A list of parameter values; may be empty, not null * @param maxRowsInFirstFrame Maximum number of rows to return in the Frame. * @return Execute result */ ExecuteResult execute(StatementHandle h, List parameterValues, int maxRowsInFirstFrame) 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. * *

If the statement handle is not known, or is already closed, does * nothing. * * @param h Statement handle */ 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-sets 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. Analogous 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. Analogous to * {@link Connection#rollback()}; * * @param ch A reference to the real JDBC Connection */ void rollback(ConnectionHandle ch); /** Synchronizes 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), /** Database property which is the Avatica version */ AVATICA_VERSION(FilteredConstants.VERSION), /** Database property containing the value of * {@link DatabaseMetaData#getDriverVersion()}. */ GET_DRIVER_VERSION(""), /** Database property containing the value of * {@link DatabaseMetaData#getDriverMinorVersion()}. */ GET_DRIVER_MINOR_VERSION(-1), /** Database property containing the value of * {@link DatabaseMetaData#getDriverMajorVersion()}. */ GET_DRIVER_MAJOR_VERSION(-1), /** Database property containing the value of * {@link DatabaseMetaData#getDriverName()}. */ GET_DRIVER_NAME(""), /** Database property containing the value of * {@link DatabaseMetaData#getDatabaseMinorVersion()}. */ GET_DATABASE_MINOR_VERSION(-1), /** Database property containing the value of * {@link DatabaseMetaData#getDatabaseMajorVersion()}. */ GET_DATABASE_MAJOR_VERSION(-1), /** Database property containing the value of * {@link DatabaseMetaData#getDatabaseProductName()}. */ GET_DATABASE_PRODUCT_NAME(""), /** Database property containing the value of * {@link DatabaseMetaData#getDatabaseProductVersion()}. */ GET_DATABASE_PRODUCT_VERSION(""); public final Class type; public final Object defaultValue; public final Method method; public final boolean isJdbc; DatabaseProperty(T defaultValue) { this.defaultValue = defaultValue; final String methodName = AvaticaUtils.toCamelCase(name()); Method localMethod = null; try { localMethod = DatabaseMetaData.class.getMethod(methodName); } catch (NoSuchMethodException e) { // Pass, localMethod stays null. } if (null == localMethod) { this.method = null; this.type = null; this.isJdbc = false; } else { this.method = localMethod; this.type = AvaticaUtils.box(method.getReturnType()); this.isJdbc = true; } // It's either: 1) not a JDBC method, 2) has no default value, // 3) the defaultValue is of the expected type assert !isJdbc || 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; } } /** * Response from a collection of SQL commands or parameter values in a single batch. */ class ExecuteBatchResult { public final long[] updateCounts; public ExecuteBatchResult(long[] updateCounts) { this.updateCounts = Objects.requireNonNull(updateCounts); } } /** 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 create(String connectionId, int statementId, boolean ownStatement, Signature signature, Frame firstFrame, long updateCount) { return new MetaResultSet(connectionId, statementId, ownStatement, Objects.requireNonNull(signature), firstFrame, updateCount); } 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 com.hazelcast.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 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; 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. */ 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 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)); } 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 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") long 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; } final Common.Row.Builder rowBuilder = Common.Row.newBuilder(); if (row instanceof Object[]) { // If only Object[] was also Iterable. for (Object element : (Object[]) row) { parseColumn(rowBuilder, element); } } else if (row instanceof Iterable) { for (Object element : (Iterable) row) { parseColumn(rowBuilder, element); } } else { // Can a "row" be a primitive? A struct? Only an Array? throw new RuntimeException("Only arrays are supported"); } // Collect all rows builder.addRows(rowBuilder.build()); } return builder.build(); } static void parseColumn(Common.Row.Builder rowBuilder, Object column) { final Common.ColumnValue.Builder columnBuilder = Common.ColumnValue.newBuilder(); if (column instanceof List) { columnBuilder.setHasArrayValue(true); List list = (List) column; // Add each element in the list/array to the column's value for (Object listItem : list) { final Common.TypedValue scalarListItem = serializeScalar(listItem); columnBuilder.addArrayValue(scalarListItem); // Add the deprecated 'value' repeated attribute for backwards compat columnBuilder.addValue(scalarListItem); } } else { // The default value, but still explicit. columnBuilder.setHasArrayValue(false); // Only one value for this column, a scalar. final Common.TypedValue scalarVal = serializeScalar(column); columnBuilder.setScalarValue(scalarVal); // Add the deprecated 'value' repeated attribute for backwards compat columnBuilder.addValue(scalarVal); } // Add value to row rowBuilder.addValue(columnBuilder.build()); } static Common.TypedValue serializeScalar(Object element) { final Common.TypedValue.Builder valueBuilder = Common.TypedValue.newBuilder(); // Let TypedValue handle the serialization for us. TypedValue.toProto(valueBuilder, element); 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) { 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 array = new ArrayList<>(column.getValueCount()); for (Common.TypedValue columnValue : column.getValueList()) { array.add(deserializeScalarValue(columnValue)); } return array; } else { return deserializeScalarValue(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)) { // The column in this row is an Array (has multiple values) List array = new ArrayList<>(column.getArrayValueCount()); for (Common.TypedValue arrayValue : column.getArrayValueList()) { // Duplicative because of the ColumnValue/TypedValue difference. if (Common.Rep.ARRAY == arrayValue.getType()) { // Each element in this Array is an Array. array.add(parseArray(arrayValue)); } else { // The array element is a scalar. array.add(deserializeScalarValue(arrayValue)); } } return array; } else { // Scalar return deserializeScalarValue(column.getScalarValue()); } } /** * Recursively parses a TypedValue while it is an array. */ static Object parseArray(Common.TypedValue array) { List convertedArray = new ArrayList<>(array.getArrayValueCount()); for (Common.TypedValue arrayElement : array.getArrayValueList()) { if (Common.Rep.ARRAY == arrayElement.getType()) { // Recurse convertedArray.add(parseArray(arrayElement)); } else { // The component type of this array is a scalar. convertedArray.add(deserializeScalarValue(arrayElement)); } } return convertedArray; } /** * 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 deserializeScalarValue(Common.TypedValue protoElement) { // ByteString is a single case where TypedValue is representing the data differently // (in its "local" form) than Frame does. We need to unwrap the Base64 encoding. if (Common.Rep.BYTE_STRING == protoElement.getType()) { // Protobuf is sending native bytes (not b64) across the wire. B64 bytes is only for // TypedValue's benefit return protoElement.getBytesValue().toByteArray(); } // Again, let TypedValue deserialize things for us. return TypedValue.fromProto(protoElement).value; } @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 rows, Iterable otherRows) { if (null == rows) { if (null != otherRows) { return false; } } else { Iterator iter1 = rows.iterator(); Iterator 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 obj2List = (List) 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 obj1List = (List) 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. */ // Visible for testing 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. * *

{@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 properties 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