http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java deleted file mode 100644 index d432e4a..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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.adapter.jdbc; - -import org.apache.calcite.DataContext; -import org.apache.calcite.adapter.java.AbstractQueryableTable; -import org.apache.calcite.adapter.java.JavaTypeFactory; -import org.apache.calcite.avatica.ColumnMetaData; -import org.apache.calcite.jdbc.CalciteConnection; -import org.apache.calcite.linq4j.Enumerable; -import org.apache.calcite.linq4j.Enumerator; -import org.apache.calcite.linq4j.QueryProvider; -import org.apache.calcite.linq4j.Queryable; -import org.apache.calcite.plan.Convention; -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.prepare.Prepare.CatalogReader; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.core.TableModify; -import org.apache.calcite.rel.core.TableModify.Operation; -import org.apache.calcite.rel.logical.LogicalTableModify; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.rel.type.RelProtoDataType; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.runtime.ResultSetEnumerable; -import org.apache.calcite.schema.ModifiableTable; -import org.apache.calcite.schema.ScannableTable; -import org.apache.calcite.schema.Schema; -import org.apache.calcite.schema.SchemaPlus; -import org.apache.calcite.schema.TranslatableTable; -import org.apache.calcite.schema.impl.AbstractTableQueryable; -import org.apache.calcite.sql.SqlIdentifier; -import org.apache.calcite.sql.SqlNodeList; -import org.apache.calcite.sql.SqlSelect; -import org.apache.calcite.sql.parser.SqlParserPos; -import org.apache.calcite.sql.pretty.SqlPrettyWriter; -import org.apache.calcite.sql.util.SqlString; -import org.apache.calcite.util.Pair; -import org.apache.calcite.util.Util; - -import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Queryable that gets its data from a table within a JDBC connection. - * - * <p>The idea is not to read the whole table, however. The idea is to use - * this as a building block for a query, by applying Queryable operators - * such as - * {@link org.apache.calcite.linq4j.Queryable#where(org.apache.calcite.linq4j.function.Predicate2)}. - * The resulting queryable can then be converted to a SQL query, which can be - * executed efficiently on the JDBC server.</p> - */ -class JdbcTable extends AbstractQueryableTable - implements TranslatableTable, ScannableTable, ModifiableTable { - private RelProtoDataType protoRowType; - private final JdbcSchema jdbcSchema; - private final String jdbcCatalogName; - private final String jdbcSchemaName; - private final String jdbcTableName; - private final Schema.TableType jdbcTableType; - - public JdbcTable(JdbcSchema jdbcSchema, String jdbcCatalogName, - String jdbcSchemaName, String tableName, Schema.TableType jdbcTableType) { - super(Object[].class); - this.jdbcSchema = jdbcSchema; - this.jdbcCatalogName = jdbcCatalogName; - this.jdbcSchemaName = jdbcSchemaName; - this.jdbcTableName = tableName; - this.jdbcTableType = Preconditions.checkNotNull(jdbcTableType); - } - - public String toString() { - return "JdbcTable {" + jdbcTableName + "}"; - } - - @Override public Schema.TableType getJdbcTableType() { - return jdbcTableType; - } - - public RelDataType getRowType(RelDataTypeFactory typeFactory) { - if (protoRowType == null) { - try { - protoRowType = - jdbcSchema.getRelDataType( - jdbcCatalogName, - jdbcSchemaName, - jdbcTableName); - } catch (SQLException e) { - throw new RuntimeException( - "Exception while reading definition of table '" + jdbcTableName - + "'", e); - } - } - return protoRowType.apply(typeFactory); - } - - private List<Pair<ColumnMetaData.Rep, Integer>> fieldClasses( - final JavaTypeFactory typeFactory) { - final RelDataType rowType = protoRowType.apply(typeFactory); - return Lists.transform(rowType.getFieldList(), - new Function<RelDataTypeField, Pair<ColumnMetaData.Rep, Integer>>() { - public Pair<ColumnMetaData.Rep, Integer> - apply(RelDataTypeField field) { - final RelDataType type = field.getType(); - final Class clazz = (Class) typeFactory.getJavaClass(type); - final ColumnMetaData.Rep rep = - Util.first(ColumnMetaData.Rep.of(clazz), - ColumnMetaData.Rep.OBJECT); - return Pair.of(rep, type.getSqlTypeName().getJdbcOrdinal()); - } - }); - } - - SqlString generateSql() { - final SqlNodeList selectList = - new SqlNodeList( - Collections.singletonList(SqlIdentifier.star(SqlParserPos.ZERO)), - SqlParserPos.ZERO); - SqlSelect node = - new SqlSelect(SqlParserPos.ZERO, SqlNodeList.EMPTY, selectList, - tableName(), null, null, null, null, null, null, null); - final SqlPrettyWriter writer = new SqlPrettyWriter(jdbcSchema.dialect); - node.unparse(writer, 0, 0); - return writer.toSqlString(); - } - - SqlIdentifier tableName() { - final List<String> strings = new ArrayList<>(); - if (jdbcSchema.catalog != null) { - strings.add(jdbcSchema.catalog); - } - if (jdbcSchema.schema != null) { - strings.add(jdbcSchema.schema); - } - strings.add(jdbcTableName); - return new SqlIdentifier(strings, SqlParserPos.ZERO); - } - - public RelNode toRel(RelOptTable.ToRelContext context, - RelOptTable relOptTable) { - return new JdbcTableScan(context.getCluster(), relOptTable, this, - jdbcSchema.convention); - } - - public <T> Queryable<T> asQueryable(QueryProvider queryProvider, - SchemaPlus schema, String tableName) { - return new JdbcTableQueryable<>(queryProvider, schema, tableName); - } - - public Enumerable<Object[]> scan(DataContext root) { - final JavaTypeFactory typeFactory = root.getTypeFactory(); - final SqlString sql = generateSql(); - return ResultSetEnumerable.of(jdbcSchema.getDataSource(), sql.getSql(), - JdbcUtils.ObjectArrayRowBuilder.factory(fieldClasses(typeFactory))); - } - - @Override public Collection getModifiableCollection() { - return null; - } - - @Override public TableModify toModificationRel(RelOptCluster cluster, - RelOptTable table, CatalogReader catalogReader, RelNode input, - Operation operation, List<String> updateColumnList, - List<RexNode> sourceExpressionList, boolean flattened) { - jdbcSchema.convention.register(cluster.getPlanner()); - - return new LogicalTableModify(cluster, cluster.traitSetOf(Convention.NONE), - table, catalogReader, input, operation, updateColumnList, - sourceExpressionList, flattened); - } - - /** Enumerable that returns the contents of a {@link JdbcTable} by connecting - * to the JDBC data source. */ - private class JdbcTableQueryable<T> extends AbstractTableQueryable<T> { - public JdbcTableQueryable(QueryProvider queryProvider, SchemaPlus schema, - String tableName) { - super(queryProvider, schema, JdbcTable.this, tableName); - } - - @Override public String toString() { - return "JdbcTableQueryable {table: " + tableName + "}"; - } - - public Enumerator<T> enumerator() { - final JavaTypeFactory typeFactory = - ((CalciteConnection) queryProvider).getTypeFactory(); - final SqlString sql = generateSql(); - //noinspection unchecked - final Enumerable<T> enumerable = (Enumerable<T>) ResultSetEnumerable.of( - jdbcSchema.getDataSource(), - sql.getSql(), - JdbcUtils.ObjectArrayRowBuilder.factory(fieldClasses(typeFactory))); - return enumerable.enumerator(); - } - } -} - -// End JdbcTable.java
http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java deleted file mode 100644 index 7ef8938..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTableScan.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.adapter.jdbc; - -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.plan.RelTraitSet; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.core.TableScan; - -import com.google.common.collect.ImmutableList; - -import java.util.List; - -/** - * Relational expression representing a scan of a table in a JDBC data source. - */ -public class JdbcTableScan extends TableScan implements JdbcRel { - final JdbcTable jdbcTable; - - protected JdbcTableScan( - RelOptCluster cluster, - RelOptTable table, - JdbcTable jdbcTable, - JdbcConvention jdbcConvention) { - super(cluster, cluster.traitSetOf(jdbcConvention), table); - this.jdbcTable = jdbcTable; - assert jdbcTable != null; - } - - @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { - assert inputs.isEmpty(); - return new JdbcTableScan( - getCluster(), table, jdbcTable, (JdbcConvention) getConvention()); - } - - public JdbcImplementor.Result implement(JdbcImplementor implementor) { - return implementor.result(jdbcTable.tableName(), - ImmutableList.of(JdbcImplementor.Clause.FROM), this, null); - } -} - -// End JdbcTableScan.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java deleted file mode 100644 index c74e741..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.adapter.jdbc; - -import org.apache.calcite.adapter.enumerable.EnumerableRel; -import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor; -import org.apache.calcite.adapter.enumerable.JavaRowFormat; -import org.apache.calcite.adapter.enumerable.PhysType; -import org.apache.calcite.adapter.enumerable.PhysTypeImpl; -import org.apache.calcite.adapter.java.JavaTypeFactory; -import org.apache.calcite.linq4j.tree.BlockBuilder; -import org.apache.calcite.linq4j.tree.Expression; -import org.apache.calcite.linq4j.tree.Expressions; -import org.apache.calcite.linq4j.tree.ParameterExpression; -import org.apache.calcite.linq4j.tree.Primitive; -import org.apache.calcite.linq4j.tree.UnaryExpression; -import org.apache.calcite.plan.ConventionTraitDef; -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptCost; -import org.apache.calcite.plan.RelOptPlanner; -import org.apache.calcite.plan.RelTraitSet; -import org.apache.calcite.prepare.CalcitePrepareImpl; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.convert.ConverterImpl; -import org.apache.calcite.rel.metadata.RelMetadataQuery; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.runtime.Hook; -import org.apache.calcite.runtime.SqlFunctions; -import org.apache.calcite.schema.Schemas; -import org.apache.calcite.sql.SqlDialect; -import org.apache.calcite.sql.type.SqlTypeName; -import org.apache.calcite.util.BuiltInMethod; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.TimeZone; - -/** - * Relational expression representing a scan of a table in a JDBC data source. - */ -public class JdbcToEnumerableConverter - extends ConverterImpl - implements EnumerableRel { - protected JdbcToEnumerableConverter( - RelOptCluster cluster, - RelTraitSet traits, - RelNode input) { - super(cluster, ConventionTraitDef.INSTANCE, traits, input); - } - - @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { - return new JdbcToEnumerableConverter( - getCluster(), traitSet, sole(inputs)); - } - - @Override public RelOptCost computeSelfCost(RelOptPlanner planner, - RelMetadataQuery mq) { - return super.computeSelfCost(planner, mq).multiplyBy(.1); - } - - public Result implement(EnumerableRelImplementor implementor, Prefer pref) { - // Generate: - // ResultSetEnumerable.of(schema.getDataSource(), "select ...") - final BlockBuilder builder0 = new BlockBuilder(false); - final JdbcRel child = (JdbcRel) getInput(); - final PhysType physType = - PhysTypeImpl.of( - implementor.getTypeFactory(), getRowType(), - pref.prefer(JavaRowFormat.CUSTOM)); - final JdbcConvention jdbcConvention = - (JdbcConvention) child.getConvention(); - String sql = generateSql(jdbcConvention.dialect); - if (CalcitePrepareImpl.DEBUG) { - System.out.println("[" + sql + "]"); - } - Hook.QUERY_PLAN.run(sql); - final Expression sql_ = - builder0.append("sql", Expressions.constant(sql)); - final int fieldCount = getRowType().getFieldCount(); - BlockBuilder builder = new BlockBuilder(); - final ParameterExpression resultSet_ = - Expressions.parameter(Modifier.FINAL, ResultSet.class, - builder.newName("resultSet")); - CalendarPolicy calendarPolicy = CalendarPolicy.of(jdbcConvention.dialect); - final Expression calendar_; - switch (calendarPolicy) { - case LOCAL: - calendar_ = - builder0.append("calendar", - Expressions.call(Calendar.class, "getInstance", - getTimeZoneExpression(implementor))); - break; - default: - calendar_ = null; - } - if (fieldCount == 1) { - final ParameterExpression value_ = - Expressions.parameter(Object.class, builder.newName("value")); - builder.add(Expressions.declare(Modifier.FINAL, value_, null)); - generateGet(implementor, physType, builder, resultSet_, 0, value_, - calendar_, calendarPolicy); - builder.add(Expressions.return_(null, value_)); - } else { - final Expression values_ = - builder.append("values", - Expressions.newArrayBounds(Object.class, 1, - Expressions.constant(fieldCount))); - for (int i = 0; i < fieldCount; i++) { - generateGet(implementor, physType, builder, resultSet_, i, - Expressions.arrayIndex(values_, Expressions.constant(i)), - calendar_, calendarPolicy); - } - builder.add( - Expressions.return_(null, values_)); - } - final ParameterExpression e_ = - Expressions.parameter(SQLException.class, builder.newName("e")); - final Expression rowBuilderFactory_ = - builder0.append("rowBuilderFactory", - Expressions.lambda( - Expressions.block( - Expressions.return_(null, - Expressions.lambda( - Expressions.block( - Expressions.tryCatch( - builder.toBlock(), - Expressions.catch_( - e_, - Expressions.throw_( - Expressions.new_( - RuntimeException.class, - e_)))))))), - resultSet_)); - final Expression enumerable = - builder0.append( - "enumerable", - Expressions.call( - BuiltInMethod.RESULT_SET_ENUMERABLE_OF.method, - Expressions.call( - Schemas.unwrap(jdbcConvention.expression, - JdbcSchema.class), - BuiltInMethod.JDBC_SCHEMA_DATA_SOURCE.method), - sql_, - rowBuilderFactory_)); - builder0.add( - Expressions.return_(null, enumerable)); - return implementor.result(physType, builder0.toBlock()); - } - - private UnaryExpression getTimeZoneExpression( - EnumerableRelImplementor implementor) { - return Expressions.convert_( - Expressions.call( - implementor.getRootExpression(), - "get", - Expressions.constant("timeZone")), - TimeZone.class); - } - - private void generateGet(EnumerableRelImplementor implementor, - PhysType physType, BlockBuilder builder, ParameterExpression resultSet_, - int i, Expression target, Expression calendar_, - CalendarPolicy calendarPolicy) { - final Primitive primitive = Primitive.ofBoxOr(physType.fieldClass(i)); - final RelDataType fieldType = - physType.getRowType().getFieldList().get(i).getType(); - final List<Expression> dateTimeArgs = new ArrayList<Expression>(); - dateTimeArgs.add(Expressions.constant(i + 1)); - SqlTypeName sqlTypeName = fieldType.getSqlTypeName(); - boolean offset = false; - switch (calendarPolicy) { - case LOCAL: - dateTimeArgs.add(calendar_); - break; - case NULL: - // We don't specify a calendar at all, so we don't add an argument and - // instead use the version of the getXXX that doesn't take a Calendar - break; - case DIRECT: - sqlTypeName = SqlTypeName.ANY; - break; - case SHIFT: - switch (sqlTypeName) { - case TIMESTAMP: - case DATE: - offset = true; - } - break; - } - final Expression source; - switch (sqlTypeName) { - case DATE: - case TIME: - case TIMESTAMP: - source = Expressions.call( - getMethod(sqlTypeName, fieldType.isNullable(), offset), - Expressions.<Expression>list() - .append( - Expressions.call(resultSet_, - getMethod2(sqlTypeName), dateTimeArgs)) - .appendIf(offset, getTimeZoneExpression(implementor))); - break; - case ARRAY: - final Expression x = Expressions.convert_( - Expressions.call(resultSet_, jdbcGetMethod(primitive), - Expressions.constant(i + 1)), - java.sql.Array.class); - source = Expressions.call(BuiltInMethod.JDBC_ARRAY_TO_LIST.method, x); - break; - default: - source = Expressions.call( - resultSet_, jdbcGetMethod(primitive), Expressions.constant(i + 1)); - } - builder.add( - Expressions.statement( - Expressions.assign( - target, source))); - - // [CALCITE-596] If primitive type columns contain null value, returns null - // object - if (primitive != null) { - builder.add( - Expressions.ifThen( - Expressions.call(resultSet_, "wasNull"), - Expressions.statement( - Expressions.assign(target, - Expressions.constant(null))))); - } - } - - private Method getMethod(SqlTypeName sqlTypeName, boolean nullable, - boolean offset) { - switch (sqlTypeName) { - case DATE: - return (nullable - ? BuiltInMethod.DATE_TO_INT_OPTIONAL - : BuiltInMethod.DATE_TO_INT).method; - case TIME: - return (nullable - ? BuiltInMethod.TIME_TO_INT_OPTIONAL - : BuiltInMethod.TIME_TO_INT).method; - case TIMESTAMP: - return (nullable - ? (offset - ? BuiltInMethod.TIMESTAMP_TO_LONG_OPTIONAL_OFFSET - : BuiltInMethod.TIMESTAMP_TO_LONG_OPTIONAL) - : (offset - ? BuiltInMethod.TIMESTAMP_TO_LONG_OFFSET - : BuiltInMethod.TIMESTAMP_TO_LONG)).method; - default: - throw new AssertionError(sqlTypeName + ":" + nullable); - } - } - - private Method getMethod2(SqlTypeName sqlTypeName) { - switch (sqlTypeName) { - case DATE: - return BuiltInMethod.RESULT_SET_GET_DATE2.method; - case TIME: - return BuiltInMethod.RESULT_SET_GET_TIME2.method; - case TIMESTAMP: - return BuiltInMethod.RESULT_SET_GET_TIMESTAMP2.method; - default: - throw new AssertionError(sqlTypeName); - } - } - - /** E,g, {@code jdbcGetMethod(int)} returns "getInt". */ - private String jdbcGetMethod(Primitive primitive) { - return primitive == null - ? "getObject" - : "get" + SqlFunctions.initcap(primitive.primitiveName); - } - - private String generateSql(SqlDialect dialect) { - final JdbcImplementor jdbcImplementor = - new JdbcImplementor(dialect, - (JavaTypeFactory) getCluster().getTypeFactory()); - final JdbcImplementor.Result result = - jdbcImplementor.visitChild(0, getInput()); - return result.asStatement().toSqlString(dialect).getSql(); - } - - /** Whether this JDBC driver needs you to pass a Calendar object to methods - * such as {@link ResultSet#getTimestamp(int, java.util.Calendar)}. */ - private enum CalendarPolicy { - NONE, - NULL, - LOCAL, - DIRECT, - SHIFT; - - static CalendarPolicy of(SqlDialect dialect) { - switch (dialect.getDatabaseProduct()) { - case MYSQL: - return SHIFT; - case HSQLDB: - default: - // NULL works for hsqldb-2.3; nothing worked for hsqldb-1.8. - return NULL; - } - } - } -} - -// End JdbcToEnumerableConverter.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverterRule.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverterRule.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverterRule.java deleted file mode 100644 index a212c3b..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverterRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.adapter.jdbc; - -import org.apache.calcite.adapter.enumerable.EnumerableConvention; -import org.apache.calcite.plan.RelTraitSet; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.convert.ConverterRule; - -/** - * Rule to convert a relational expression from - * {@link JdbcConvention} to - * {@link EnumerableConvention}. - */ -public class JdbcToEnumerableConverterRule extends ConverterRule { - JdbcToEnumerableConverterRule(JdbcConvention out) { - super(RelNode.class, out, EnumerableConvention.INSTANCE, - "JdbcToEnumerableConverterRule:" + out); - } - - @Override public RelNode convert(RelNode rel) { - RelTraitSet newTraitSet = rel.getTraitSet().replace(getOutTrait()); - return new JdbcToEnumerableConverter(rel.getCluster(), newTraitSet, rel); - } -} - -// End JdbcToEnumerableConverterRule.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcUtils.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcUtils.java deleted file mode 100644 index bb8e558..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcUtils.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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.adapter.jdbc; - -import org.apache.calcite.avatica.ColumnMetaData; -import org.apache.calcite.avatica.util.DateTimeUtils; -import org.apache.calcite.linq4j.function.Function0; -import org.apache.calcite.linq4j.function.Function1; -import org.apache.calcite.sql.SqlDialect; -import org.apache.calcite.util.ImmutableNullableList; -import org.apache.calcite.util.Pair; - -import org.apache.commons.dbcp.BasicDataSource; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.Date; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import javax.annotation.Nonnull; -import javax.sql.DataSource; - -/** - * Utilities for the JDBC provider. - */ -final class JdbcUtils { - private JdbcUtils() { - throw new AssertionError("no instances!"); - } - - /** Pool of dialects. */ - static class DialectPool { - final Map<DataSource, SqlDialect> map0 = new IdentityHashMap<>(); - final Map<List, SqlDialect> map = new HashMap<>(); - - public static final DialectPool INSTANCE = new DialectPool(); - - SqlDialect get(DataSource dataSource) { - final SqlDialect sqlDialect = map0.get(dataSource); - if (sqlDialect != null) { - return sqlDialect; - } - Connection connection = null; - try { - connection = dataSource.getConnection(); - DatabaseMetaData metaData = connection.getMetaData(); - String productName = metaData.getDatabaseProductName(); - String productVersion = metaData.getDatabaseProductVersion(); - List key = ImmutableList.of(productName, productVersion); - SqlDialect dialect = map.get(key); - if (dialect == null) { - dialect = SqlDialect.create(metaData); - map.put(key, dialect); - map0.put(dataSource, dialect); - } - connection.close(); - connection = null; - return dialect; - } catch (SQLException e) { - throw new RuntimeException(e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - // ignore - } - } - } - } - } - - /** Builder that calls {@link ResultSet#getObject(int)} for every column, - * or {@code getXxx} if the result type is a primitive {@code xxx}, - * and returns an array of objects for each row. */ - static class ObjectArrayRowBuilder implements Function0<Object[]> { - private final ResultSet resultSet; - private final int columnCount; - private final ColumnMetaData.Rep[] reps; - private final int[] types; - - ObjectArrayRowBuilder(ResultSet resultSet, ColumnMetaData.Rep[] reps, - int[] types) - throws SQLException { - this.resultSet = resultSet; - this.reps = reps; - this.types = types; - this.columnCount = resultSet.getMetaData().getColumnCount(); - } - - public static Function1<ResultSet, Function0<Object[]>> factory( - final List<Pair<ColumnMetaData.Rep, Integer>> list) { - return new Function1<ResultSet, Function0<Object[]>>() { - public Function0<Object[]> apply(ResultSet resultSet) { - try { - return new ObjectArrayRowBuilder( - resultSet, - Pair.left(list).toArray(new ColumnMetaData.Rep[list.size()]), - Ints.toArray(Pair.right(list))); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - }; - } - - public Object[] apply() { - try { - final Object[] values = new Object[columnCount]; - for (int i = 0; i < columnCount; i++) { - values[i] = value(i); - } - return values; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - /** - * Gets a value from a given column in a JDBC result set. - * - * @param i Ordinal of column (1-based, per JDBC) - */ - private Object value(int i) throws SQLException { - // MySQL returns timestamps shifted into local time. Using - // getTimestamp(int, Calendar) with a UTC calendar should prevent this, - // but does not. So we shift explicitly. - switch (types[i]) { - case Types.TIMESTAMP: - return shift(resultSet.getTimestamp(i + 1)); - case Types.TIME: - return shift(resultSet.getTime(i + 1)); - case Types.DATE: - return shift(resultSet.getDate(i + 1)); - } - return reps[i].jdbcGet(resultSet, i + 1); - } - - private static Timestamp shift(Timestamp v) { - if (v == null) { - return null; - } - long time = v.getTime(); - int offset = TimeZone.getDefault().getOffset(time); - return new Timestamp(time + offset); - } - - private static Time shift(Time v) { - if (v == null) { - return null; - } - long time = v.getTime(); - int offset = TimeZone.getDefault().getOffset(time); - return new Time((time + offset) % DateTimeUtils.MILLIS_PER_DAY); - } - - private static Date shift(Date v) { - if (v == null) { - return null; - } - long time = v.getTime(); - int offset = TimeZone.getDefault().getOffset(time); - return new Date(time + offset); - } - } - - /** Ensures that if two data sources have the same definition, they will use - * the same object. - * - * <p>This in turn makes it easier to cache - * {@link org.apache.calcite.sql.SqlDialect} objects. Otherwise, each time we - * see a new data source, we have to open a connection to find out what - * database product and version it is. */ - static class DataSourcePool { - public static final DataSourcePool INSTANCE = new DataSourcePool(); - - private final LoadingCache<List<String>, BasicDataSource> cache = - CacheBuilder.newBuilder().softValues().build( - new CacheLoader<List<String>, BasicDataSource>() { - @Override public BasicDataSource load(@Nonnull List<String> key) { - BasicDataSource dataSource = new BasicDataSource(); - dataSource.setUrl(key.get(0)); - dataSource.setUsername(key.get(1)); - dataSource.setPassword(key.get(2)); - dataSource.setDriverClassName(key.get(3)); - return dataSource; - } - }); - - public DataSource get(String url, String driverClassName, - String username, String password) { - // Get data source objects from a cache, so that we don't have to sniff - // out what kind of database they are quite as often. - final List<String> key = - ImmutableNullableList.of(url, username, password, driverClassName); - return cache.getUnchecked(key); - } - } -} - -// End JdbcUtils.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/jdbc/package-info.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/package-info.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/package-info.java deleted file mode 100644 index ba2bdd8..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -/** - * Query provider based on a JDBC data source. - */ -@PackageMarker -package org.apache.calcite.adapter.jdbc; - -import org.apache.calcite.avatica.util.PackageMarker; - -// End package-info.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/adapter/package-info.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/package-info.java b/core/src/main/java/org/apache/calcite/adapter/package-info.java deleted file mode 100644 index 29084db..0000000 --- a/core/src/main/java/org/apache/calcite/adapter/package-info.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -/** - * Calcite adapters. - * - * <p>An adapter allows Calcite to access data in a particular data source as - * if it were a collection of tables in a schema. Each adapter typically - * contains an implementation of {@link org.apache.calcite.schema.SchemaFactory} - * and some classes that implement other schema SPIs. - * - * <p>To use an adapter, include a custom schema in a JSON model file: - * - * <blockquote><pre> - * schemas: [ - * { - * type: 'custom', - * name: 'My Custom Schema', - * factory: 'com.acme.MySchemaFactory', - * operand: {a: 'foo', b: [1, 3.5] } - * } - * ] - * </pre> - * </blockquote> - */ -@PackageMarker -package org.apache.calcite.adapter; - -import org.apache.calcite.avatica.util.PackageMarker; - -// End package-info.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java new file mode 100644 index 0000000..df03b03 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java @@ -0,0 +1,94 @@ +/* + * 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.AvaticaRuntimeException; +import org.apache.calcite.avatica.remote.Service.ErrorResponse; +import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse; + +import java.util.Collections; +import java.util.List; + +/** + * The client-side representation of {@link AvaticaRuntimeException}. This exception is not intended + * for consumption by clients, {@link AvaticaSqlException} serves that purpose. This exception only + * exists to pass the original error attributes to a higher level of execution without modifying + * existing exception-handling logic. + */ +public class AvaticaClientRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final int errorCode; + private final String sqlState; + private final AvaticaSeverity severity; + private final List<String> serverExceptions; + private final RpcMetadataResponse metadata; + + public AvaticaClientRuntimeException(String errorMessage, int errorCode, String sqlState, + AvaticaSeverity severity, List<String> serverExceptions, RpcMetadataResponse metadata) { + super(errorMessage); + this.errorCode = errorCode; + this.sqlState = sqlState; + this.severity = severity; + this.serverExceptions = serverExceptions; + this.metadata = metadata; + } + + public AvaticaClientRuntimeException(String message, Throwable cause) { + super(message, cause); + errorCode = ErrorResponse.UNKNOWN_ERROR_CODE; + sqlState = ErrorResponse.UNKNOWN_SQL_STATE; + severity = AvaticaSeverity.UNKNOWN; + serverExceptions = Collections.singletonList(""); + metadata = null; + } + + public int getErrorCode() { + return errorCode; + } + + public String getSqlState() { + return sqlState; + } + + public AvaticaSeverity getSeverity() { + return severity; + } + + public List<String> getServerExceptions() { + return serverExceptions; + } + + public RpcMetadataResponse getRpcMetadata() { + return metadata; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append(getClass().getSimpleName()).append(": ") + .append(getMessage()).append(". Error ").append(getErrorCode()) + .append(" (").append(sqlState).append(") ").append(getSeverity()).append("\n\n"); + for (String serverException : getServerExceptions()) { + sb.append(serverException).append("\n"); + } + return sb.toString(); + } + +} + +// End AvaticaClientRuntimeException.java http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/fc7b26c8/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java new file mode 100644 index 0000000..51649c1 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java @@ -0,0 +1,769 @@ +/* + * 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.ExecuteBatchResult; +import org.apache.calcite.avatica.Meta.MetaResultSet; +import org.apache.calcite.avatica.remote.KerberosConnection; +import org.apache.calcite.avatica.remote.Service; +import org.apache.calcite.avatica.remote.Service.ErrorResponse; +import org.apache.calcite.avatica.remote.Service.OpenConnectionRequest; +import org.apache.calcite.avatica.remote.TypedValue; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Implementation of JDBC connection + * for the Avatica framework. + * + * <p>Abstract to allow newer versions of JDBC to add methods. + */ +public abstract class AvaticaConnection implements Connection { + + /** The name of the sole column returned by DML statements, containing + * the number of rows modified. */ + public static final String ROWCOUNT_COLUMN_NAME = "ROWCOUNT"; + + public static final String NUM_EXECUTE_RETRIES_KEY = "avatica.statement.retries"; + public static final String NUM_EXECUTE_RETRIES_DEFAULT = "5"; + + /** The name of the sole column returned by an EXPLAIN statement. + * + * <p>Actually Avatica does not care what this column is called, but here is + * a useful place to define a suggested value. */ + public static final String PLAN_COLUMN_NAME = "PLAN"; + + protected int statementCount; + private boolean closed; + private int holdability; + private int networkTimeout; + private KerberosConnection kerberosConnection; + private Service service; + + public final String id; + public final Meta.ConnectionHandle handle; + protected final UnregisteredDriver driver; + protected final AvaticaFactory factory; + final String url; + protected final Properties info; + protected final Meta meta; + protected final AvaticaSpecificDatabaseMetaData metaData; + public final Helper helper = Helper.INSTANCE; + public final Map<InternalProperty, Object> properties = new HashMap<>(); + public final Map<Integer, AvaticaStatement> statementMap = + new ConcurrentHashMap<>(); + final Map<Integer, AtomicBoolean> flagMap = new ConcurrentHashMap<>(); + protected final long maxRetriesPerExecute; + + /** + * Creates an AvaticaConnection. + * + * <p>Not public; method is called only from the driver or a derived + * class.</p> + * + * @param driver Driver + * @param factory Factory for JDBC objects + * @param url Server URL + * @param info Other connection properties + */ + protected AvaticaConnection(UnregisteredDriver driver, + AvaticaFactory factory, + String url, + Properties info) { + this.id = UUID.randomUUID().toString(); + this.handle = new Meta.ConnectionHandle(this.id); + this.driver = driver; + this.factory = factory; + this.url = url; + this.info = info; + this.meta = driver.createMeta(this); + this.metaData = factory.newDatabaseMetaData(this); + try { + this.holdability = metaData.getResultSetHoldability(); + } catch (SQLException e) { + // We know the impl doesn't throw this. + throw new RuntimeException(e); + } + this.maxRetriesPerExecute = getNumStatementRetries(info); + } + + /** Computes the number of retries + * {@link AvaticaStatement#executeInternal(Meta.Signature, boolean)} + * should retry before failing. */ + long getNumStatementRetries(Properties props) { + return Long.valueOf(Objects.requireNonNull(props) + .getProperty(NUM_EXECUTE_RETRIES_KEY, NUM_EXECUTE_RETRIES_DEFAULT)); + } + + /** Returns a view onto this connection's configuration properties. Code + * in Avatica and derived projects should use this view rather than calling + * {@link java.util.Properties#getProperty(String)}. Derived projects will + * almost certainly subclass {@link ConnectionConfig} with their own + * properties. */ + public ConnectionConfig config() { + return new ConnectionConfigImpl(info); + } + + /** + * Opens the connection on the server. + */ + public void openConnection() { + // Open the connection on the server + this.meta.openConnection(handle, OpenConnectionRequest.serializeProperties(info)); + } + + // Connection methods + + public AvaticaStatement createStatement() throws SQLException { + //noinspection MagicConstant + return createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + holdability); + } + + public PreparedStatement prepareStatement(String sql) throws SQLException { + //noinspection MagicConstant + return prepareStatement( + sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, + holdability); + } + + public CallableStatement prepareCall(String sql) throws SQLException { + throw helper.unsupported(); + } + + public String nativeSQL(String sql) throws SQLException { + throw helper.unsupported(); + } + + public void setAutoCommit(boolean autoCommit) throws SQLException { + meta.connectionSync(handle, new ConnectionPropertiesImpl().setAutoCommit(autoCommit)); + } + + public boolean getAutoCommit() throws SQLException { + return unbox(sync().isAutoCommit(), true); + } + + public void commit() throws SQLException { + meta.commit(handle); + } + + public void rollback() throws SQLException { + meta.rollback(handle); + } + + public void close() throws SQLException { + if (!closed) { + closed = true; + + // Per specification, if onConnectionClose throws, this method will throw + // a SQLException, but statement will still be closed. + try { + meta.closeConnection(handle); + driver.handler.onConnectionClose(this); + if (null != kerberosConnection) { + kerberosConnection.stopRenewalThread(); + } + } catch (RuntimeException e) { + throw helper.createException("While closing connection", e); + } + } + } + + public boolean isClosed() throws SQLException { + return closed; + } + + public DatabaseMetaData getMetaData() throws SQLException { + return metaData; + } + + public void setReadOnly(boolean readOnly) throws SQLException { + meta.connectionSync(handle, new ConnectionPropertiesImpl().setReadOnly(readOnly)); + } + + public boolean isReadOnly() throws SQLException { + return unbox(sync().isReadOnly(), true); + } + + public void setCatalog(String catalog) throws SQLException { + meta.connectionSync(handle, new ConnectionPropertiesImpl().setCatalog(catalog)); + } + + public String getCatalog() { + return sync().getCatalog(); + } + + public void setTransactionIsolation(int level) throws SQLException { + meta.connectionSync(handle, new ConnectionPropertiesImpl().setTransactionIsolation(level)); + } + + public int getTransactionIsolation() throws SQLException { + //noinspection MagicConstant + return unbox(sync().getTransactionIsolation(), TRANSACTION_NONE); + } + + public SQLWarning getWarnings() throws SQLException { + return null; + } + + public void clearWarnings() throws SQLException { + // no-op since connection pooling often calls this. + } + + public Statement createStatement( + int resultSetType, int resultSetConcurrency) throws SQLException { + //noinspection MagicConstant + return createStatement(resultSetType, resultSetConcurrency, holdability); + } + + public PreparedStatement prepareStatement( + String sql, + int resultSetType, + int resultSetConcurrency) throws SQLException { + //noinspection MagicConstant + return prepareStatement( + sql, resultSetType, resultSetConcurrency, holdability); + } + + public CallableStatement prepareCall( + String sql, + int resultSetType, + int resultSetConcurrency) throws SQLException { + throw helper.unsupported(); + } + + public Map<String, Class<?>> getTypeMap() throws SQLException { + throw helper.unsupported(); + } + + public void setTypeMap(Map<String, Class<?>> map) throws SQLException { + throw helper.unsupported(); + } + + public void setHoldability(int holdability) throws SQLException { + if (!(holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT + || holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT)) { + throw new SQLException("invalid value"); + } + this.holdability = holdability; + } + + public int getHoldability() throws SQLException { + return holdability; + } + + public Savepoint setSavepoint() throws SQLException { + throw helper.unsupported(); + } + + public Savepoint setSavepoint(String name) throws SQLException { + throw helper.unsupported(); + } + + public void rollback(Savepoint savepoint) throws SQLException { + throw helper.unsupported(); + } + + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + throw helper.unsupported(); + } + + public AvaticaStatement createStatement( + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + return factory.newStatement(this, null, resultSetType, resultSetConcurrency, + resultSetHoldability); + } + + public PreparedStatement prepareStatement( + String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + try { + final Meta.StatementHandle h = meta.prepare(handle, sql, -1); + return factory.newPreparedStatement(this, h, h.signature, resultSetType, + resultSetConcurrency, resultSetHoldability); + } catch (RuntimeException e) { + throw helper.createException("while preparing SQL: " + sql, e); + } + } + + public CallableStatement prepareCall( + String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + throw helper.unsupported(); + } + + public PreparedStatement prepareStatement( + String sql, int autoGeneratedKeys) throws SQLException { + throw helper.unsupported(); + } + + public PreparedStatement prepareStatement( + String sql, int[] columnIndexes) throws SQLException { + throw helper.unsupported(); + } + + public PreparedStatement prepareStatement( + String sql, String[] columnNames) throws SQLException { + throw helper.unsupported(); + } + + public Clob createClob() throws SQLException { + throw helper.unsupported(); + } + + public Blob createBlob() throws SQLException { + throw helper.unsupported(); + } + + public NClob createNClob() throws SQLException { + throw helper.unsupported(); + } + + public SQLXML createSQLXML() throws SQLException { + throw helper.unsupported(); + } + + public boolean isValid(int timeout) throws SQLException { + throw helper.unsupported(); + } + + public void setClientInfo(String name, String value) + throws SQLClientInfoException { + throw helper.clientInfo(); + } + + public void setClientInfo(Properties properties) + throws SQLClientInfoException { + throw helper.clientInfo(); + } + + public String getClientInfo(String name) throws SQLException { + throw helper.unsupported(); + } + + public Properties getClientInfo() throws SQLException { + throw helper.unsupported(); + } + + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException { + throw helper.unsupported(); + } + + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException { + throw helper.unsupported(); + } + + public void setSchema(String schema) throws SQLException { + meta.connectionSync(handle, new ConnectionPropertiesImpl().setSchema(schema)); + } + + public String getSchema() { + return sync().getSchema(); + } + + public void abort(Executor executor) throws SQLException { + throw helper.unsupported(); + } + + public void setNetworkTimeout( + Executor executor, int milliseconds) throws SQLException { + this.networkTimeout = milliseconds; + } + + public int getNetworkTimeout() throws SQLException { + return networkTimeout; + } + + public <T> T unwrap(Class<T> iface) throws SQLException { + if (iface.isInstance(this)) { + return iface.cast(this); + } + throw helper.createException( + "does not implement '" + iface + "'"); + } + + public boolean isWrapperFor(Class<?> iface) throws SQLException { + return iface.isInstance(this); + } + + /** Returns the time zone of this connection. Determines the offset applied + * when converting datetime values from the database into + * {@link java.sql.Timestamp} values. */ + public TimeZone getTimeZone() { + final String timeZoneName = config().timeZone(); + return timeZoneName == null + ? TimeZone.getDefault() + : TimeZone.getTimeZone(timeZoneName); + } + + /** + * Executes a prepared query, closing any previously open result set. + * + * @param statement Statement + * @param signature Prepared query + * @param firstFrame First frame of rows, or null if we need to execute + * @param state The state used to create the given result + * @param isUpdate Was the caller context via {@link PreparedStatement#executeUpdate()}. + * @return Result set + * @throws java.sql.SQLException if a database error occurs + */ + protected ResultSet executeQueryInternal(AvaticaStatement statement, + Meta.Signature signature, Meta.Frame firstFrame, QueryState state, boolean isUpdate) + throws SQLException { + // Close the previous open result set, if there is one. + Meta.Frame frame = firstFrame; + Meta.Signature signature2 = signature; + + synchronized (statement) { + if (statement.openResultSet != null) { + final AvaticaResultSet rs = statement.openResultSet; + statement.openResultSet = null; + try { + rs.close(); + } catch (Exception e) { + throw helper.createException( + "Error while closing previous result set", e); + } + } + + try { + if (statement.isWrapperFor(AvaticaPreparedStatement.class)) { + final AvaticaPreparedStatement pstmt = (AvaticaPreparedStatement) statement; + Meta.StatementHandle handle = pstmt.handle; + if (isUpdate) { + // Make a copy of the StatementHandle, nulling out the Signature. + // CALCITE-1086 we don't need to send the Signature to the server + // when we're only performing an update. Saves on serialization. + handle = new Meta.StatementHandle(handle.connectionId, handle.id, null); + } + final Meta.ExecuteResult executeResult = + meta.execute(handle, pstmt.getParameterValues(), + statement.getFetchSize()); + final MetaResultSet metaResultSet = executeResult.resultSets.get(0); + frame = metaResultSet.firstFrame; + statement.updateCount = metaResultSet.updateCount; + signature2 = executeResult.resultSets.get(0).signature; + } + } catch (Exception e) { + e.printStackTrace(); + throw helper.createException(e.getMessage(), e); + } + + final TimeZone timeZone = getTimeZone(); + if (frame == null && signature2 == null && statement.updateCount != -1) { + statement.openResultSet = null; + } else { + // Duplicative SQL, for support non-prepared statements + statement.openResultSet = + factory.newResultSet(statement, state, signature2, timeZone, frame); + } + } + // Release the monitor before executing, to give another thread the + // opportunity to call cancel. + try { + if (statement.openResultSet != null) { + statement.openResultSet.execute(); + isUpdateCapable(statement); + } + } catch (Exception e) { + throw helper.createException( + "exception while executing query: " + e.getMessage(), e); + } + return statement.openResultSet; + } + + /** Executes a batch update using an {@link AvaticaPreparedStatement}. + * + * @param pstmt The prepared statement. + * @return An array of update counts containing one element for each command in the batch. + */ + protected long[] executeBatchUpdateInternal(AvaticaPreparedStatement pstmt) throws SQLException { + try { + // Get the handle from the statement + Meta.StatementHandle handle = pstmt.handle; + // Execute it against meta + return meta.executeBatch(handle, pstmt.getParameterValueBatch()).updateCounts; + } catch (Exception e) { + throw helper.createException(e.getMessage(), e); + } + } + + /** Returns whether a a statement is capable of updates and if so, + * and the statement's {@code updateCount} is still -1, proceeds to + * get updateCount value from statement's resultSet. + * + * <p>Handles "ROWCOUNT" object as Number or List + * + * @param statement Statement + * @throws SQLException on error + */ + private void isUpdateCapable(final AvaticaStatement statement) + throws SQLException { + Meta.Signature signature = statement.getSignature(); + if (signature == null || signature.statementType == null) { + return; + } + if (signature.statementType.canUpdate() && statement.updateCount == -1) { + statement.openResultSet.next(); + Object obj = statement.openResultSet.getObject(ROWCOUNT_COLUMN_NAME); + if (obj instanceof Number) { + statement.updateCount = ((Number) obj).intValue(); + } else if (obj instanceof List) { + @SuppressWarnings("unchecked") + final List<Number> numbers = (List<Number>) obj; + statement.updateCount = numbers.get(0).intValue(); + } else { + throw helper.createException("Not a valid return result."); + } + statement.openResultSet = null; + } + } + + protected Meta.ExecuteResult prepareAndExecuteInternal( + final AvaticaStatement statement, final String sql, long maxRowCount) + throws SQLException, NoSuchStatementException { + final Meta.PrepareCallback callback = + new Meta.PrepareCallback() { + public Object getMonitor() { + return statement; + } + + public void clear() throws SQLException { + if (statement.openResultSet != null) { + final AvaticaResultSet rs = statement.openResultSet; + statement.openResultSet = null; + try { + rs.close(); + } catch (Exception e) { + throw helper.createException( + "Error while closing previous result set", e); + } + } + } + + public void assign(Meta.Signature signature, Meta.Frame firstFrame, + long updateCount) throws SQLException { + statement.setSignature(signature); + + if (updateCount != -1) { + statement.updateCount = updateCount; + } else { + final TimeZone timeZone = getTimeZone(); + statement.openResultSet = factory.newResultSet(statement, new QueryState(sql), + signature, timeZone, firstFrame); + } + } + + public void execute() throws SQLException { + if (statement.openResultSet != null) { + statement.openResultSet.execute(); + isUpdateCapable(statement); + } + } + }; + // The old semantics were that maxRowCount was also treated as the maximum number of + // elements in the first Frame of results. A value of -1 would also preserve this, but an + // explicit (positive) number is easier to follow, IMO. + return meta.prepareAndExecute(statement.handle, sql, maxRowCount, + AvaticaUtils.toSaturatedInt(maxRowCount), callback); + } + + protected ExecuteBatchResult prepareAndUpdateBatch(final AvaticaStatement statement, + final List<String> queries) throws NoSuchStatementException, SQLException { + return meta.prepareAndExecuteBatch(statement.handle, queries); + } + + protected ResultSet createResultSet(Meta.MetaResultSet metaResultSet, QueryState state) + throws SQLException { + final Meta.StatementHandle h = new Meta.StatementHandle( + metaResultSet.connectionId, metaResultSet.statementId, null); + final AvaticaStatement statement = lookupStatement(h); + // These are all the metadata operations, no updates + ResultSet resultSet = executeQueryInternal(statement, metaResultSet.signature.sanitize(), + metaResultSet.firstFrame, state, false); + if (metaResultSet.ownStatement) { + resultSet.getStatement().closeOnCompletion(); + } + return resultSet; + } + + /** Creates a statement wrapper around an existing handle. */ + protected AvaticaStatement lookupStatement(Meta.StatementHandle h) + throws SQLException { + final AvaticaStatement statement = statementMap.get(h.id); + if (statement != null) { + return statement; + } + //noinspection MagicConstant + return factory.newStatement(this, Objects.requireNonNull(h), + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, holdability); + } + + // do not make public + protected static Trojan createTrojan() { + return new Trojan(); + } + + /** Converts a {@link Boolean} to a {@code boolean}, with a default value. */ + private boolean unbox(Boolean b, boolean defaultValue) { + return b == null ? defaultValue : b; + } + + /** Converts an {@link Integer} to an {@code int}, with a default value. */ + private int unbox(Integer i, int defaultValue) { + return i == null ? defaultValue : i; + } + + private Meta.ConnectionProperties sync() { + return meta.connectionSync(handle, new ConnectionPropertiesImpl()); + } + + /** Returns or creates a slot whose state can be changed to cancel a + * statement. Statements will receive the same slot if and only if their id + * is the same. */ + public AtomicBoolean getCancelFlag(Meta.StatementHandle h) + throws NoSuchStatementException { + AvaticaUtils.upgrade("after dropping JDK 1.7, use Map.computeIfAbsent"); + synchronized (flagMap) { + AtomicBoolean b = flagMap.get(h.id); + if (b == null) { + b = new AtomicBoolean(); + flagMap.put(h.id, b); + } + return b; + } + } + + /** A way to call package-protected methods. But only a sub-class of + * connection can create one. */ + public static class Trojan { + // must be private + private Trojan() { + } + + /** A means for anyone who has a trojan to call the protected method + * {@link org.apache.calcite.avatica.AvaticaResultSet#execute()}. + * @throws SQLException if execute fails for some reason. */ + public ResultSet execute(AvaticaResultSet resultSet) throws SQLException { + return resultSet.execute(); + } + + /** A means for anyone who has a trojan to call the protected method + * {@link org.apache.calcite.avatica.AvaticaStatement#getParameterValues()}. + */ + public List<TypedValue> getParameterValues(AvaticaStatement statement) { + return statement.getParameterValues(); + } + + /** A means for anyone who has a trojan to get the protected field + * {@link org.apache.calcite.avatica.AvaticaConnection#meta}. */ + public Meta getMeta(AvaticaConnection connection) { + return connection.meta; + } + } + + /** + * A Callable-like interface but without a "throws Exception". + * + * @param <T> The return type from {@code call}. + */ + public interface CallableWithoutException<T> { + T call(); + } + + /** + * Invokes the given "callable", retrying the call when the server responds with an error + * denoting that the connection is missing on the server. + * + * @param callable The function to invoke. + * @return The value from the result of the callable. + */ + public <T> T invokeWithRetries(CallableWithoutException<T> callable) { + RuntimeException lastException = null; + for (int i = 0; i < maxRetriesPerExecute; i++) { + try { + return callable.call(); + } catch (AvaticaClientRuntimeException e) { + lastException = e; + if (ErrorResponse.MISSING_CONNECTION_ERROR_CODE == e.getErrorCode()) { + this.openConnection(); + continue; + } + throw e; + } + } + if (null != lastException) { + throw lastException; + } else { + // Shouldn't ever happen. + throw new IllegalStateException(); + } + } + + public void setKerberosConnection(KerberosConnection kerberosConnection) { + this.kerberosConnection = Objects.requireNonNull(kerberosConnection); + } + + public KerberosConnection getKerberosConnection() { + return this.kerberosConnection; + } + + public Service getService() { + assert null != service; + return service; + } + + public void setService(Service service) { + this.service = Objects.requireNonNull(service); + } +} + +// End AvaticaConnection.java
