http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java new file mode 100644 index 0000000..a999f19 --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java @@ -0,0 +1,329 @@ +/* + * 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.util.UnsynchronizedBuffer; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.AbstractList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Avatica utilities. */ +public class AvaticaUtils { + private static final Map<Class, Class> BOX; + + private static final MethodHandle SET_LARGE_MAX_ROWS = + method(void.class, Statement.class, "setLargeMaxRows", long.class); + private static final MethodHandle GET_LARGE_MAX_ROWS = + method(long.class, Statement.class, "getLargeMaxRows"); + private static final MethodHandle GET_LARGE_UPDATE_COUNT = + method(void.class, Statement.class, "getLargeUpdateCount"); + + private static final Set<String> UNIQUE_STRINGS = new HashSet<>(); + + private static final ThreadLocal<byte[]> PER_THREAD_BUFFER = new ThreadLocal<byte[]>() { + @Override protected byte[] initialValue() { + return new byte[4096]; + } + }; + + private AvaticaUtils() {} + + static { + BOX = new HashMap<>(); + BOX.put(boolean.class, Boolean.class); + BOX.put(byte.class, Byte.class); + BOX.put(char.class, Character.class); + BOX.put(short.class, Short.class); + BOX.put(int.class, Integer.class); + BOX.put(long.class, Long.class); + BOX.put(float.class, Float.class); + BOX.put(double.class, Double.class); + } + + private static MethodHandle method(Class returnType, Class targetType, + String name, Class... argTypes) { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + return lookup.findVirtual(targetType, name, + MethodType.methodType(returnType, targetType, argTypes)); + } catch (NoSuchMethodException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Does nothing with its argument. Call this method when you have a value + * you are not interested in, but you don't want the compiler to warn that + * you are not using it. + */ + public static void discard(Object o) { + if (false) { + discard(o); + } + } + + /** + * Adapts a primitive array into a {@link List}. For example, + * {@code asList(new double[2])} returns a {@code List<Double>}. + */ + public static List<?> primitiveList(final Object array) { + // REVIEW: A per-type list might be more efficient. (Or might not.) + return new AbstractList() { + public Object get(int index) { + return java.lang.reflect.Array.get(array, index); + } + + public int size() { + return java.lang.reflect.Array.getLength(array); + } + }; + } + + /** + * Converts a camelCase name into an upper-case underscore-separated name. + * For example, {@code camelToUpper("myJdbcDriver")} returns + * "MY_JDBC_DRIVER". + */ + public static String camelToUpper(String name) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (Character.isUpperCase(c)) { + buf.append('_'); + } else { + c = Character.toUpperCase(c); + } + buf.append(c); + } + return buf.toString(); + } + + /** + * Converts an underscore-separated name into a camelCase name. + * For example, {@code uncamel("MY_JDBC_DRIVER")} returns "myJdbcDriver". + */ + public static String toCamelCase(String name) { + StringBuilder buf = new StringBuilder(); + int nextUpper = -1; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c == '_') { + nextUpper = i + 1; + continue; + } + if (nextUpper == i) { + c = Character.toUpperCase(c); + } else { + c = Character.toLowerCase(c); + } + buf.append(c); + } + return buf.toString(); + } + + /** Returns the boxed class. For example, {@code box(int.class)} + * returns {@code java.lang.Integer}. */ + public static Class box(Class clazz) { + if (clazz.isPrimitive()) { + return BOX.get(clazz); + } + return clazz; + } + + /** Creates an instance of a plugin class. First looks for a static + * member called INSTANCE, then calls a public default constructor. + * + * <p>If className contains a "#" instead looks for a static field. + * + * @param pluginClass Class (or interface) to instantiate + * @param className Name of implementing class + * @param <T> Class + * @return Plugin instance + */ + public static <T> T instantiatePlugin(Class<T> pluginClass, + String className) { + try { + // Given a static field, say "com.example.MyClass#FOO_INSTANCE", return + // the value of that static field. + if (className.contains("#")) { + try { + int i = className.indexOf('#'); + String left = className.substring(0, i); + String right = className.substring(i + 1); + //noinspection unchecked + final Class<T> clazz = (Class) Class.forName(left); + final Field field; + field = clazz.getField(right); + return pluginClass.cast(field.get(null)); + } catch (NoSuchFieldException e) { + // ignore + } + } + //noinspection unchecked + final Class<T> clazz = (Class) Class.forName(className); + assert pluginClass.isAssignableFrom(clazz); + try { + // We assume that if there is an INSTANCE field it is static and + // has the right type. + final Field field = clazz.getField("INSTANCE"); + return pluginClass.cast(field.get(null)); + } catch (NoSuchFieldException e) { + // ignore + } + return clazz.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Property '" + className + + "' not valid for plugin type " + pluginClass.getName(), e); + } + } + + /** Reads the contents of an input stream and returns as a string. */ + public static String readFully(InputStream inputStream) throws IOException { + return readFully(inputStream, new UnsynchronizedBuffer(1024)); + } + + /** Reads the contents of an input stream and returns as a string. */ + public static String readFully(InputStream inputStream, UnsynchronizedBuffer buffer) + throws IOException { + // Variant that lets us use a pooled Buffer + final byte[] bytes = _readFully(inputStream, buffer); + return new String(bytes, 0, bytes.length, StandardCharsets.UTF_8); + } + + /** Reads the contents of an input stream and returns as a string. */ + public static byte[] readFullyToBytes(InputStream inputStream) throws IOException { + return readFullyToBytes(inputStream, new UnsynchronizedBuffer(1024)); + } + + /** Reads the contents of an input stream and returns as a string. */ + public static byte[] readFullyToBytes(InputStream inputStream, UnsynchronizedBuffer buffer) + throws IOException { + // Variant that lets us use a pooled Buffer + return _readFully(inputStream, buffer); + } + + /** + * Reads the contents of an input stream and returns a byte array. + * + * @param inputStream the input to read from. + * @return A byte array whose length is equal to the number of bytes contained. + */ + static byte[] _readFully(InputStream inputStream, UnsynchronizedBuffer buffer) + throws IOException { + final byte[] bytes = PER_THREAD_BUFFER.get(); + for (;;) { + int count = inputStream.read(bytes, 0, bytes.length); + if (count < 0) { + break; + } + buffer.write(bytes, 0, count); + } + return buffer.toArray(); + } + + /** Invokes {@code Statement#setLargeMaxRows}, falling back on + * {@link Statement#setMaxRows(int)} if the method does not exist (before + * JDK 1.8) or throws {@link UnsupportedOperationException}. */ + public static void setLargeMaxRows(Statement statement, long n) + throws SQLException { + if (SET_LARGE_MAX_ROWS != null) { + try { + // Call Statement.setLargeMaxRows + SET_LARGE_MAX_ROWS.invokeExact(n); + return; + } catch (UnsupportedOperationException e) { + // ignore, and fall through to call Statement.setMaxRows + } catch (Error | RuntimeException | SQLException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + int i = (int) Math.max(Math.min(n, Integer.MAX_VALUE), Integer.MIN_VALUE); + statement.setMaxRows(i); + } + + /** Invokes {@code Statement#getLargeMaxRows}, falling back on + * {@link Statement#getMaxRows()} if the method does not exist (before + * JDK 1.8) or throws {@link UnsupportedOperationException}. */ + public static long getLargeMaxRows(Statement statement) throws SQLException { + if (GET_LARGE_MAX_ROWS != null) { + try { + // Call Statement.getLargeMaxRows + return (long) GET_LARGE_MAX_ROWS.invokeExact(); + } catch (UnsupportedOperationException e) { + // ignore, and fall through to call Statement.getMaxRows + } catch (Error | RuntimeException | SQLException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + return statement.getMaxRows(); + } + + /** Invokes {@code Statement#getLargeUpdateCount}, falling back on + * {@link Statement#getUpdateCount()} if the method does not exist (before + * JDK 1.8) or throws {@link UnsupportedOperationException}. */ + public static long getLargeUpdateCount(Statement statement) + throws SQLException { + if (GET_LARGE_UPDATE_COUNT != null) { + try { + // Call Statement.getLargeUpdateCount + return (long) GET_LARGE_UPDATE_COUNT.invokeExact(); + } catch (UnsupportedOperationException e) { + // ignore, and fall through to call Statement.getUpdateCount + } catch (Error | RuntimeException | SQLException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + return statement.getUpdateCount(); + } + + /** Generates a string that is unique in the execution of the JVM. + * It is used by tests to ensure that they create distinct temporary tables. + * The strings are never thrown away, so don't put too much in there! + * Thread safe. */ + public static String unique(String base) { + synchronized (UNIQUE_STRINGS) { + String s = base; + while (!UNIQUE_STRINGS.add(s)) { + s = base + "_" + UNIQUE_STRINGS.size(); + } + return s; + } + } +} + +// End AvaticaUtils.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java b/avatica/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java new file mode 100644 index 0000000..086ae4a --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java @@ -0,0 +1,103 @@ +/* + * 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.AvaticaHttpClientFactoryImpl; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.apache.calcite.avatica.ConnectionConfigImpl.PropEnv; +import static org.apache.calcite.avatica.ConnectionConfigImpl.parse; + +/** + * Enumeration of Avatica's built-in connection properties. + */ +public enum BuiltInConnectionProperty implements ConnectionProperty { + /** Factory. */ + FACTORY("factory", Type.PLUGIN, null, false), + + /** Name of initial schema. */ + SCHEMA("schema", Type.STRING, null, false), + + /** Time zone, for example 'gmt-3'. Default is the JVM's time zone. */ + TIME_ZONE("timeZone", Type.STRING, null, false), + + /** Remote URL. */ + URL("url", Type.STRING, null, false), + + /** Serialization used over remote connections */ + SERIALIZATION("serialization", Type.STRING, "json", false), + + /** Factory for constructing http clients. */ + HTTP_CLIENT_FACTORY("httpclient_factory", Type.PLUGIN, + AvaticaHttpClientFactoryImpl.class.getName(), false), + + /** HttpClient implementation class name. */ + HTTP_CLIENT_IMPL("httpclient_impl", Type.STRING, null, false); + + private final String camelName; + private final Type type; + private final Object defaultValue; + private final boolean required; + + /** Deprecated; use {@link #TIME_ZONE}. */ + @Deprecated // to be removed before 2.0 + public static final BuiltInConnectionProperty TIMEZONE = TIME_ZONE; + + private static final Map<String, BuiltInConnectionProperty> NAME_TO_PROPS; + + static { + NAME_TO_PROPS = new HashMap<>(); + for (BuiltInConnectionProperty p : BuiltInConnectionProperty.values()) { + NAME_TO_PROPS.put(p.camelName.toUpperCase(), p); + NAME_TO_PROPS.put(p.name(), p); + } + } + + BuiltInConnectionProperty(String camelName, Type type, Object defaultValue, + boolean required) { + this.camelName = camelName; + this.type = type; + this.defaultValue = defaultValue; + this.required = required; + assert defaultValue == null || type.valid(defaultValue); + } + + public String camelName() { + return camelName; + } + + public Object defaultValue() { + return defaultValue; + } + + public Type type() { + return type; + } + + public boolean required() { + return required; + } + + public PropEnv wrap(Properties properties) { + return new PropEnv(parse(properties, NAME_TO_PROPS), this); + } +} + +// End BuiltInConnectionProperty.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java new file mode 100644 index 0000000..bcdc228 --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java @@ -0,0 +1,593 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica; + +import org.apache.calcite.avatica.proto.Common; +import org.apache.calcite.avatica.util.ByteString; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.lang.reflect.Type; +import java.sql.Array; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Struct; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Metadata for a column. + * + * <p>(Compare with {@link java.sql.ResultSetMetaData}.) + */ +public class ColumnMetaData { + private static final FieldDescriptor CATALOG_NAME_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.CATALOG_NAME_FIELD_NUMBER); + private static final FieldDescriptor SCHEMA_NAME_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.SCHEMA_NAME_FIELD_NUMBER); + private static final FieldDescriptor LABEL_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.LABEL_FIELD_NUMBER); + private static final FieldDescriptor COLUMN_NAME_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.COLUMN_NAME_FIELD_NUMBER); + private static final FieldDescriptor TABLE_NAME_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.TABLE_NAME_FIELD_NUMBER); + private static final FieldDescriptor COLUMN_CLASS_NAME_DESCRIPTOR = Common.ColumnMetaData + .getDescriptor().findFieldByNumber(Common.ColumnMetaData.COLUMN_CLASS_NAME_FIELD_NUMBER); + + public final int ordinal; // 0-based + public final boolean autoIncrement; + public final boolean caseSensitive; + public final boolean searchable; + public final boolean currency; + public final int nullable; + public final boolean signed; + public final int displaySize; + public final String label; + public final String columnName; + public final String schemaName; + public final int precision; + public final int scale; + public final String tableName; + public final String catalogName; + public final boolean readOnly; + public final boolean writable; + public final boolean definitelyWritable; + public final String columnClassName; + public final AvaticaType type; + + @JsonCreator + public ColumnMetaData( + @JsonProperty("ordinal") int ordinal, + @JsonProperty("autoIncrement") boolean autoIncrement, + @JsonProperty("caseSensitive") boolean caseSensitive, + @JsonProperty("searchable") boolean searchable, + @JsonProperty("currency") boolean currency, + @JsonProperty("nullable") int nullable, + @JsonProperty("signed") boolean signed, + @JsonProperty("displaySize") int displaySize, + @JsonProperty("label") String label, + @JsonProperty("columnName") String columnName, + @JsonProperty("schemaName") String schemaName, + @JsonProperty("precision") int precision, + @JsonProperty("scale") int scale, + @JsonProperty("tableName") String tableName, + @JsonProperty("catalogName") String catalogName, + @JsonProperty("type") AvaticaType type, + @JsonProperty("readOnly") boolean readOnly, + @JsonProperty("writable") boolean writable, + @JsonProperty("definitelyWritable") boolean definitelyWritable, + @JsonProperty("columnClassName") String columnClassName) { + this.ordinal = ordinal; + this.autoIncrement = autoIncrement; + this.caseSensitive = caseSensitive; + this.searchable = searchable; + this.currency = currency; + this.nullable = nullable; + this.signed = signed; + this.displaySize = displaySize; + this.label = label; + // Per the JDBC spec this should be just columnName. + // For example, the query + // select 1 as x, c as y from t + // should give columns + // (label=x, column=null, table=null) + // (label=y, column=c table=t) + // But DbUnit requires every column to have a name. Duh. + this.columnName = first(columnName, label); + this.schemaName = schemaName; + this.precision = precision; + this.scale = scale; + this.tableName = tableName; + this.catalogName = catalogName; + this.type = type; + this.readOnly = readOnly; + this.writable = writable; + this.definitelyWritable = definitelyWritable; + this.columnClassName = columnClassName; + } + + public Common.ColumnMetaData toProto() { + Common.ColumnMetaData.Builder builder = Common.ColumnMetaData.newBuilder(); + + // Primitive fields (can't be null) + builder.setOrdinal(ordinal) + .setAutoIncrement(autoIncrement) + .setCaseSensitive(caseSensitive) + .setSearchable(searchable) + .setCurrency(currency) + .setNullable(nullable) + .setSigned(signed) + .setDisplaySize(displaySize) + .setPrecision(precision) + .setScale(scale) + .setReadOnly(readOnly) + .setWritable(writable) + .setDefinitelyWritable(definitelyWritable); + + // Potentially null fields + if (null != label) { + builder.setLabel(label); + } + + if (null != columnName) { + builder.setColumnName(columnName); + } + + if (null != schemaName) { + builder.setSchemaName(schemaName); + } + + if (null != tableName) { + builder.setTableName(tableName); + } + + if (null != catalogName) { + builder.setCatalogName(catalogName); + } + + if (null != type) { + builder.setType(type.toProto()); + } + + if (null != columnClassName) { + builder.setColumnClassName(columnClassName); + } + + return builder.build(); + } + + public static ColumnMetaData fromProto(Common.ColumnMetaData proto) { + AvaticaType nestedType = AvaticaType.fromProto(proto.getType()); + + String catalogName = null; + if (proto.hasField(CATALOG_NAME_DESCRIPTOR)) { + catalogName = proto.getCatalogName(); + } + + String schemaName = null; + if (proto.hasField(SCHEMA_NAME_DESCRIPTOR)) { + schemaName = proto.getSchemaName(); + } + + String label = null; + if (proto.hasField(LABEL_DESCRIPTOR)) { + label = proto.getLabel(); + } + + String columnName = null; + if (proto.hasField(COLUMN_NAME_DESCRIPTOR)) { + columnName = proto.getColumnName(); + } + + String tableName = null; + if (proto.hasField(TABLE_NAME_DESCRIPTOR)) { + tableName = proto.getTableName(); + } + + String columnClassName = null; + if (proto.hasField(COLUMN_CLASS_NAME_DESCRIPTOR)) { + columnClassName = proto.getColumnClassName(); + } + + // Recreate the ColumnMetaData + return new ColumnMetaData(proto.getOrdinal(), proto.getAutoIncrement(), + proto.getCaseSensitive(), proto.getSearchable(), proto.getCurrency(), proto.getNullable(), + proto.getSigned(), proto.getDisplaySize(), label, columnName, + schemaName, proto.getPrecision(), proto.getScale(), tableName, + catalogName, nestedType, proto.getReadOnly(), proto.getWritable(), + proto.getDefinitelyWritable(), columnClassName); + } + + @Override public int hashCode() { + return Objects.hash(autoIncrement, caseSensitive, catalogName, + columnClassName, columnName, currency, definitelyWritable, displaySize, + label, nullable, ordinal, precision, readOnly, scale, schemaName, + searchable, signed, tableName, type, writable); + } + + @Override public boolean equals(Object o) { + return o == this + || o instanceof ColumnMetaData + && autoIncrement == ((ColumnMetaData) o).autoIncrement + && caseSensitive == ((ColumnMetaData) o).caseSensitive + && Objects.equals(catalogName, ((ColumnMetaData) o).catalogName) + && Objects.equals(columnClassName, ((ColumnMetaData) o).columnClassName) + && Objects.equals(columnName, ((ColumnMetaData) o).columnName) + && currency == ((ColumnMetaData) o).currency + && definitelyWritable == ((ColumnMetaData) o).definitelyWritable + && displaySize == ((ColumnMetaData) o).displaySize + && Objects.equals(label, ((ColumnMetaData) o).label) + && nullable == ((ColumnMetaData) o).nullable + && ordinal == ((ColumnMetaData) o).ordinal + && precision == ((ColumnMetaData) o).precision + && readOnly == ((ColumnMetaData) o).readOnly + && scale == ((ColumnMetaData) o).scale + && Objects.equals(schemaName, ((ColumnMetaData) o).schemaName) + && searchable == ((ColumnMetaData) o).searchable + && signed == ((ColumnMetaData) o).signed + && Objects.equals(tableName, ((ColumnMetaData) o).tableName) + && Objects.equals(type, ((ColumnMetaData) o).type) + && writable == ((ColumnMetaData) o).writable; + } + + private static <T> T first(T t0, T t1) { + return t0 != null ? t0 : t1; + } + + /** Creates a {@link ScalarType}. */ + public static ScalarType scalar(int type, String typeName, Rep rep) { + return new ScalarType(type, typeName, rep); + } + + /** Creates a {@link StructType}. */ + public static StructType struct(List<ColumnMetaData> columns) { + return new StructType(columns); + } + + /** Creates an {@link ArrayType}. */ + public static ArrayType array(AvaticaType componentType, String typeName, + Rep rep) { + return new ArrayType(Types.ARRAY, typeName, rep, componentType); + } + + /** Creates a ColumnMetaData for result sets that are not based on a struct + * but need to have a single 'field' for purposes of + * {@link java.sql.ResultSetMetaData}. */ + public static ColumnMetaData dummy(AvaticaType type, boolean nullable) { + return new ColumnMetaData( + 0, + false, + true, + false, + false, + nullable + ? DatabaseMetaData.columnNullable + : DatabaseMetaData.columnNoNulls, + true, + -1, + null, + null, + null, + -1, + -1, + null, + null, + type, + true, + false, + false, + type.columnClassName()); + } + + public ColumnMetaData setRep(Rep rep) { + return new ColumnMetaData(ordinal, autoIncrement, caseSensitive, searchable, + currency, nullable, signed, displaySize, label, columnName, schemaName, + precision, scale, tableName, catalogName, type.setRep(rep), readOnly, + writable, definitelyWritable, columnClassName); + } + + /** Description of the type used to internally represent a value. For example, + * a {@link java.sql.Date} might be represented as a {@link #PRIMITIVE_INT} + * if not nullable, or a {@link #JAVA_SQL_DATE}. */ + public enum Rep { + PRIMITIVE_BOOLEAN(boolean.class), + PRIMITIVE_BYTE(byte.class), + PRIMITIVE_CHAR(char.class), + PRIMITIVE_SHORT(short.class), + PRIMITIVE_INT(int.class), + PRIMITIVE_LONG(long.class), + PRIMITIVE_FLOAT(float.class), + PRIMITIVE_DOUBLE(double.class), + BOOLEAN(Boolean.class), + BYTE(Byte.class), + CHARACTER(Character.class), + SHORT(Short.class), + INTEGER(Integer.class), + LONG(Long.class), + FLOAT(Float.class), + DOUBLE(Double.class), + JAVA_SQL_TIME(Time.class), + JAVA_SQL_TIMESTAMP(Timestamp.class), + JAVA_SQL_DATE(java.sql.Date.class), + JAVA_UTIL_DATE(java.util.Date.class), + BYTE_STRING(ByteString.class), + STRING(String.class), + + /** Values are represented as some sub-class of {@link Number}. + * The JSON encoding does this. */ + NUMBER(Number.class), + + ARRAY(Array.class), + MULTISET(List.class), + STRUCT(Struct.class), + + OBJECT(Object.class); + + public final Class clazz; + + public static final Map<Class, Rep> VALUE_MAP; + + static { + Map<Class, Rep> builder = new HashMap<>(); + for (Rep rep : values()) { + builder.put(rep.clazz, rep); + } + VALUE_MAP = Collections.unmodifiableMap(builder); + } + + Rep(Class clazz) { + this.clazz = clazz; + } + + public static Rep of(Type clazz) { + //noinspection SuspiciousMethodCalls + final Rep rep = VALUE_MAP.get(clazz); + return rep != null ? rep : OBJECT; + } + + /** Returns the value of a column of this type from a result set. */ + public Object jdbcGet(ResultSet resultSet, int i) throws SQLException { + switch (this) { + case PRIMITIVE_BOOLEAN: + return resultSet.getBoolean(i); + case PRIMITIVE_BYTE: + return resultSet.getByte(i); + case PRIMITIVE_SHORT: + return resultSet.getShort(i); + case PRIMITIVE_INT: + return resultSet.getInt(i); + case PRIMITIVE_LONG: + return resultSet.getLong(i); + case PRIMITIVE_FLOAT: + return resultSet.getFloat(i); + case PRIMITIVE_DOUBLE: + return resultSet.getDouble(i); + case BOOLEAN: + final boolean aBoolean = resultSet.getBoolean(i); + return resultSet.wasNull() ? null : aBoolean; + case BYTE: + final byte aByte = resultSet.getByte(i); + return resultSet.wasNull() ? null : aByte; + case SHORT: + final short aShort = resultSet.getShort(i); + return resultSet.wasNull() ? null : aShort; + case INTEGER: + final int anInt = resultSet.getInt(i); + return resultSet.wasNull() ? null : anInt; + case LONG: + final long aLong = resultSet.getLong(i); + return resultSet.wasNull() ? null : aLong; + case FLOAT: + final float aFloat = resultSet.getFloat(i); + return resultSet.wasNull() ? null : aFloat; + case DOUBLE: + final double aDouble = resultSet.getDouble(i); + return resultSet.wasNull() ? null : aDouble; + case JAVA_SQL_DATE: + return resultSet.getDate(i); + case JAVA_SQL_TIME: + return resultSet.getTime(i); + case JAVA_SQL_TIMESTAMP: + return resultSet.getTimestamp(i); + case ARRAY: + return resultSet.getArray(i); + case STRUCT: + return resultSet.getObject(i, Struct.class); + default: + return resultSet.getObject(i); + } + } + + public Common.Rep toProto() { + return Common.Rep.valueOf(name()); + } + + public static Rep fromProto(Common.Rep proto) { + return Rep.valueOf(proto.name()); + } + } + + /** Base class for a column type. */ + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type", + defaultImpl = ScalarType.class) + @JsonSubTypes({ + @JsonSubTypes.Type(value = ScalarType.class, name = "scalar"), + @JsonSubTypes.Type(value = StructType.class, name = "struct"), + @JsonSubTypes.Type(value = ArrayType.class, name = "array") }) + public static class AvaticaType { + public final int id; + public final String name; + + /** The type of the field that holds the value. Not a JDBC property. */ + public final Rep rep; + + public AvaticaType(int id, String name, Rep rep) { + this.id = id; + this.name = Objects.requireNonNull(name); + this.rep = Objects.requireNonNull(rep); + } + + public String columnClassName() { + return SqlType.valueOf(id).boxedClass().getName(); + } + + public AvaticaType setRep(Rep rep) { + throw new UnsupportedOperationException(); + } + + public Common.AvaticaType toProto() { + Common.AvaticaType.Builder builder = Common.AvaticaType.newBuilder(); + + builder.setName(name); + builder.setId(id); + builder.setRep(rep.toProto()); + + return builder.build(); + } + + public static AvaticaType fromProto(Common.AvaticaType proto) { + Common.Rep repProto = proto.getRep(); + Rep rep = Rep.valueOf(repProto.name()); + AvaticaType type; + + if (proto.hasComponent()) { + // ArrayType + // recurse on the type for the array elements + AvaticaType nestedType = AvaticaType.fromProto(proto.getComponent()); + type = ColumnMetaData.array(nestedType, proto.getName(), rep); + } else if (proto.getColumnsCount() > 0) { + // StructType + List<ColumnMetaData> columns = new ArrayList<>(proto.getColumnsCount()); + for (Common.ColumnMetaData protoColumn : proto.getColumnsList()) { + columns.add(ColumnMetaData.fromProto(protoColumn)); + } + type = ColumnMetaData.struct(columns); + } else { + // ScalarType + type = ColumnMetaData.scalar(proto.getId(), proto.getName(), rep); + } + + return type; + } + + @Override public int hashCode() { + return Objects.hash(id, name, rep); + } + + @Override public boolean equals(Object o) { + return o == this + || o instanceof AvaticaType + && id == ((AvaticaType) o).id + && Objects.equals(name, ((AvaticaType) o).name) + && rep == ((AvaticaType) o).rep; + } + } + + /** Scalar type. */ + public static class ScalarType extends AvaticaType { + @JsonCreator + public ScalarType(@JsonProperty("id") int id, + @JsonProperty("name") String name, + @JsonProperty("rep") Rep rep) { + super(id, name, rep); + } + + @Override public AvaticaType setRep(Rep rep) { + return new ScalarType(id, name, rep); + } + } + + /** Record type. */ + public static class StructType extends AvaticaType { + public final List<ColumnMetaData> columns; + + @JsonCreator + public StructType(List<ColumnMetaData> columns) { + super(Types.STRUCT, "STRUCT", ColumnMetaData.Rep.OBJECT); + this.columns = columns; + } + + @Override public Common.AvaticaType toProto() { + Common.AvaticaType.Builder builder = Common.AvaticaType.newBuilder(super.toProto()); + for (ColumnMetaData valueType : columns) { + builder.addColumns(valueType.toProto()); + } + return builder.build(); + } + + @Override public int hashCode() { + return Objects.hash(id, name, rep, columns); + } + + @Override public boolean equals(Object o) { + return o == this + || o instanceof StructType + && super.equals(o) + && Objects.equals(columns, ((StructType) o).columns); + } + } + + /** Array type. */ + public static class ArrayType extends AvaticaType { + public final AvaticaType component; + + /** + * Not for public use. Use {@link ColumnMetaData#array(AvaticaType, String, Rep)}. + */ + @JsonCreator + public ArrayType(@JsonProperty("type") int type, @JsonProperty("name") String typeName, + @JsonProperty("rep") Rep representation, @JsonProperty("component") AvaticaType component) { + super(type, typeName, representation); + this.component = component; + } + + @Override public Common.AvaticaType toProto() { + Common.AvaticaType.Builder builder = Common.AvaticaType.newBuilder(super.toProto()); + + builder.setComponent(component.toProto()); + + return builder.build(); + } + + @Override public int hashCode() { + return Objects.hash(id, name, rep, component); + } + + @Override public boolean equals(Object o) { + return o == this + || o instanceof ArrayType + && super.equals(o) + && Objects.equals(component, ((ArrayType) o).component); + } + } +} + +// End ColumnMetaData.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectStringParser.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectStringParser.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectStringParser.java new file mode 100644 index 0000000..0145024 --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectStringParser.java @@ -0,0 +1,391 @@ +/* + * 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 java.sql.SQLException; +import java.util.Map; +import java.util.Properties; + +/** + * ConnectStringParser is a utility class that parses or creates a JDBC connect + * string according to the + * <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms722656(v=vs.85).aspx"> + * OLE DB Connection String Syntax</a>. + * + * <p>This code was adapted from Mondrian's mondrian.olap.Util class. + * The primary differences between this and its Mondrian progenitor are: + * + * <ul> + * <li>use of regular {@link Properties} for compatibility with the JDBC API + * (replaces Mondrian's use of its own order-preserving and case-insensitive + * PropertyList)</li> + * + * <li>ability to pass to {@link #parse} a pre-existing Properties object into + * which properties are to be parsed, possibly overriding prior values</li> + * + * <li>use of {@link SQLException}s rather than unchecked + * {@link RuntimeException}s</li> + * + * <li>static members for parsing and creating connect strings</li> + * + * </ul> + * + * <p>ConnectStringParser has a private constructor. Callers use the static + * members: + * + * <dl> + * <dt>{@link #parse(String)} + * <dd>Parses the connect string into a new Properties object. + * + * <dt>{@link #parse(String, Properties)} + * <dd>Parses the connect string into an existing Properties object. + * + * <dt>{@link #getParamString(Properties)} + * <dd>Returns a param string, quoted and escaped as needed, to represent the + * supplied name-value pairs. + * </dl> + */ +public class ConnectStringParser { + //~ Instance fields -------------------------------------------------------- + + private final String s; + private final int n; + private int i; + private final StringBuilder nameBuf = new StringBuilder(); + private final StringBuilder valueBuf = new StringBuilder(); + + //~ Constructors ----------------------------------------------------------- + + /** + * Creates a new connect string parser. + * + * @param s connect string to parse + * + * @see #parse(String) + * @see #parse(String, Properties) + */ + private ConnectStringParser(String s) { + this.s = s; + this.i = 0; + this.n = s.length(); + } + + //~ Methods ---------------------------------------------------------------- + + /** + * Parses the connect string into a new Properties object. + * + * @param s connect string to parse + * + * @return properties object with parsed params + * + * @throws SQLException error parsing name-value pairs + */ + public static Properties parse(String s) + throws SQLException { + return new ConnectStringParser(s).parseInternal(null); + } + + /** + * Parses the connect string into an existing Properties object. + * + * @param s connect string to parse + * @param props optional properties object, may be <code>null</code> + * + * @return properties object with parsed params; if an input <code> + * props</code> was supplied, any duplicate properties will have been + * replaced by those from the connect string. + * + * @throws SQLException error parsing name-value pairs + */ + public static Properties parse(String s, Properties props) + throws SQLException { + return new ConnectStringParser(s).parseInternal(props); + } + + /** + * Parses the connect string into a Properties object. Note that the string + * can only be parsed once. Subsequent calls return empty/unchanged + * Properties. + * + * @param props optional properties object, may be <code>null</code> + * + * @return properties object with parsed params; if an input <code> + * props</code> was supplied, any duplicate properties will have been + * replaced by those from the connect string. + * + * @throws SQLException error parsing name-value pairs + */ + Properties parseInternal(Properties props) + throws SQLException { + if (props == null) { + props = new Properties(); + } + while (i < n) { + parsePair(props); + } + return props; + } + + /** + * Reads "name=value;" or "name=value<EOF>". + * + * @throws SQLException error parsing value + */ + void parsePair(Properties props) + throws SQLException { + String name = parseName(); + String value; + if (i >= n) { + value = ""; + } else if (s.charAt(i) == ';') { + i++; + value = ""; + } else { + value = parseValue(); + } + props.put(name, value); + } + + /** + * Reads "name=". Name can contain equals sign if equals sign is doubled. + */ + String parseName() { + nameBuf.setLength(0); + while (true) { + char c = s.charAt(i); + switch (c) { + case '=': + i++; + if ((i < n) && ((c = s.charAt(i)) == '=')) { + // doubled equals sign; take one of them, and carry on + i++; + nameBuf.append(c); + break; + } + String name = nameBuf.toString(); + name = name.trim(); + return name; + case ' ': + if (nameBuf.length() == 0) { + // ignore preceding spaces + i++; + break; + } + // fall through + default: + nameBuf.append(c); + i++; + if (i >= n) { + return nameBuf.toString().trim(); + } + } + } + } + + /** + * Reads "value;" or "value<EOF>" + * + * @throws SQLException if find an unterminated quoted value + */ + String parseValue() + throws SQLException { + char c; + + // skip over leading white space + while ((c = s.charAt(i)) == ' ') { + i++; + if (i >= n) { + return ""; + } + } + if ((c == '"') || (c == '\'')) { + String value = parseQuoted(c); + + // skip over trailing white space + while (i < n && s.charAt(i) == ' ') { + i++; + } + if (i >= n) { + return value; + } else if (s.charAt(i) == ';') { + i++; + return value; + } else { + throw new SQLException( + "quoted value ended too soon, at position " + i + + " in '" + s + "'"); + } + } else { + String value; + int semi = s.indexOf(';', i); + if (semi >= 0) { + value = s.substring(i, semi); + i = semi + 1; + } else { + value = s.substring(i); + i = n; + } + return value.trim(); + } + } + + /** + * Reads a string quoted by a given character. Occurrences of the quoting + * character must be doubled. For example, <code>parseQuoted('"')</code> + * reads <code>"a ""new"" string"</code> and returns <code>a "new" + * string</code>. + * + * @throws SQLException if find an unterminated quoted value + */ + String parseQuoted(char q) + throws SQLException { + char c = s.charAt(i++); + if (c != q) { + throw new AssertionError("c != q: c=" + c + " q=" + q); + } + valueBuf.setLength(0); + while (i < n) { + c = s.charAt(i); + if (c == q) { + i++; + if (i < n) { + c = s.charAt(i); + if (c == q) { + valueBuf.append(c); + i++; + continue; + } + } + return valueBuf.toString(); + } else { + valueBuf.append(c); + i++; + } + } + throw new SQLException( + "Connect string '" + s + + "' contains unterminated quoted value '" + + valueBuf.toString() + "'"); + } + + /** + * Returns a param string, quoted and escaped as needed, to represent the + * supplied name-value pairs. + * + * @param props name-value pairs + * + * @return param string, never <code>null</code> + */ + public static String getParamString(Properties props) { + if (props == null) { + return ""; + } + + StringBuilder buf = new StringBuilder(); + for (Map.Entry<String, String> entry : toMap(props).entrySet()) { + final String name = entry.getKey(); + final String value = entry.getValue(); + String quote = ""; + if (buf.length() > 0) { + buf.append(';'); + } + + // write parameter name + if (name.startsWith(" ") || name.endsWith(" ")) { + quote = "'"; + buf.append(quote); + } + int len = name.length(); + for (int i = 0; i < len; ++i) { + char c = name.charAt(i); + if (c == '=') { + buf.append('='); + } + buf.append(c); + } + + buf.append(quote); // might be empty + quote = ""; + + buf.append('='); + + // write parameter value + len = value.length(); + boolean hasSemi = value.indexOf(';') >= 0; + boolean hasSQ = value.indexOf('\'') >= 0; + boolean hasDQ = value.indexOf('"') >= 0; + if (value.startsWith(" ") || value.endsWith(" ")) { + quote = "'"; + } else if (hasSemi || hasSQ || hasDQ) { + // try to choose the least painful quote + if (value.startsWith("\"")) { + quote = "'"; + } else if (value.startsWith("'")) { + quote = "\""; + } else { + quote = hasSQ ? "\"" : "'"; + } + } + char q; + if (quote.length() > 0) { + buf.append(quote); + q = quote.charAt(0); + } else { + q = '\0'; + } + for (int i = 0; i < len; ++i) { + char c = value.charAt(i); + if (c == q) { + buf.append(q); + } + buf.append(c); + } + buf.append(quote); // might be empty + } + + return buf.toString(); + } + + /** + * Converts a {@link Properties} object to a <code>{@link Map}<String, + * String></code>. + * + * <p>This is necessary because {@link Properties} is a dinosaur class. It + * ought to extend <code>Map<String,String></code>, but instead + * extends <code>{@link java.util.Hashtable}<Object,Object></code>. + * + * <p>Typical usage, to iterate over a {@link Properties}: + * + * <blockquote> + * <code> + * Properties properties;<br> + * for (Map.Entry<String, String> entry = + * Util.toMap(properties).entrySet()) {<br> + * println("key=" + entry.getKey() + ", value=" + entry.getValue());<br> + * } + * </code> + * </blockquote> + */ + public static Map<String, String> toMap( + final Properties properties) { + //noinspection unchecked + return (Map) properties; + } +} + +// End ConnectStringParser.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java new file mode 100644 index 0000000..8e4790c --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java @@ -0,0 +1,40 @@ +/* + * 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.AvaticaHttpClientFactory; +import org.apache.calcite.avatica.remote.Service; + +/** + * Connection configuration. + */ +public interface ConnectionConfig { + /** @see BuiltInConnectionProperty#SCHEMA */ + String schema(); + /** @see BuiltInConnectionProperty#TIME_ZONE */ + String timeZone(); + /** @see BuiltInConnectionProperty#FACTORY */ + Service.Factory factory(); + /** @see BuiltInConnectionProperty#URL */ + String url(); + /** @see BuiltInConnectionProperty#SERIALIZATION */ + String serialization(); + AvaticaHttpClientFactory httpClientFactory(); + String httpClientClass(); +} + +// End ConnectionConfig.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java new file mode 100644 index 0000000..bbeefea --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java @@ -0,0 +1,239 @@ +/* + * 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.AvaticaHttpClientFactory; +import org.apache.calcite.avatica.remote.Service; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +/** Implementation of {@link ConnectionConfig}. */ +public class ConnectionConfigImpl implements ConnectionConfig { + protected final Properties properties; + + public ConnectionConfigImpl(Properties properties) { + this.properties = properties; + } + + public String schema() { + return BuiltInConnectionProperty.SCHEMA.wrap(properties).getString(); + } + + public String timeZone() { + return BuiltInConnectionProperty.TIME_ZONE.wrap(properties).getString(); + } + + public Service.Factory factory() { + return BuiltInConnectionProperty.FACTORY.wrap(properties) + .getPlugin(Service.Factory.class, null); + } + + public String url() { + return BuiltInConnectionProperty.URL.wrap(properties).getString(); + } + + public String serialization() { + return BuiltInConnectionProperty.SERIALIZATION.wrap(properties).getString(); + } + + public AvaticaHttpClientFactory httpClientFactory() { + return BuiltInConnectionProperty.HTTP_CLIENT_FACTORY.wrap(properties) + .getPlugin(AvaticaHttpClientFactory.class, null); + } + + public String httpClientClass() { + return BuiltInConnectionProperty.HTTP_CLIENT_IMPL.wrap(properties).getString(); + } + + /** Converts a {@link Properties} object containing (name, value) + * pairs into a map whose keys are + * {@link org.apache.calcite.avatica.InternalProperty} objects. + * + * <p>Matching is case-insensitive. Throws if a property is not known. + * If a property occurs more than once, takes the last occurrence.</p> + * + * @param properties Properties + * @return Map + * @throws RuntimeException if a property is not known + */ + public static Map<ConnectionProperty, String> parse(Properties properties, + Map<String, ? extends ConnectionProperty> nameToProps) { + final Map<ConnectionProperty, String> map = + new LinkedHashMap<ConnectionProperty, String>(); + for (String name : properties.stringPropertyNames()) { + final ConnectionProperty connectionProperty = + nameToProps.get(name.toUpperCase()); + if (connectionProperty == null) { + // For now, don't throw. It messes up sub-projects. + //throw new RuntimeException("Unknown property '" + name + "'"); + continue; + } + map.put(connectionProperty, properties.getProperty(name)); + } + return map; + } + + /** The combination of a property definition and a map of property values. */ + public static class PropEnv { + final Map<? extends ConnectionProperty, String> map; + private final ConnectionProperty property; + + public PropEnv(Map<? extends ConnectionProperty, String> map, + ConnectionProperty property) { + this.map = map; + this.property = property; + } + + private <T> T get_(Converter<T> converter, String defaultValue) { + final String s = map.get(property); + if (s != null) { + return converter.apply(property, s); + } + return converter.apply(property, defaultValue); + } + + /** Returns the string value of this property, or null if not specified and + * no default. */ + public String getString() { + return getString((String) property.defaultValue()); + } + + /** Returns the string value of this property, or null if not specified and + * no default. */ + public String getString(String defaultValue) { + assert property.type() == ConnectionProperty.Type.STRING; + return get_(IDENTITY_CONVERTER, defaultValue); + } + + /** Returns the boolean value of this property. Throws if not set and no + * default. */ + public boolean getBoolean() { + return getBoolean((Boolean) property.defaultValue()); + } + + /** Returns the boolean value of this property. Throws if not set and no + * default. */ + public boolean getBoolean(boolean defaultValue) { + assert property.type() == ConnectionProperty.Type.BOOLEAN; + return get_(BOOLEAN_CONVERTER, Boolean.toString(defaultValue)); + } + + /** Returns the enum value of this property. Throws if not set and no + * default. */ + public <E extends Enum<E>> E getEnum(Class<E> enumClass) { + //noinspection unchecked + return getEnum(enumClass, (E) property.defaultValue()); + } + + /** Returns the enum value of this property. Throws if not set and no + * default. */ + public <E extends Enum<E>> E getEnum(Class<E> enumClass, E defaultValue) { + assert property.type() == ConnectionProperty.Type.ENUM; + //noinspection unchecked + return get_(enumConverter(enumClass), defaultValue.name()); + } + + /** Returns an instance of a plugin. + * + * <p>Throws if not set and no default. + * Also throws if the class does not implement the required interface, + * or if it does not have a public default constructor or an public static + * field called {@code #INSTANCE}. */ + public <T> T getPlugin(Class<T> pluginClass, T defaultInstance) { + return getPlugin(pluginClass, (String) property.defaultValue(), + defaultInstance); + } + + /** Returns an instance of a plugin, using a given class name if none is + * set. + * + * <p>Throws if not set and no default. + * Also throws if the class does not implement the required interface, + * or if it does not have a public default constructor or an public static + * field called {@code #INSTANCE}. */ + public <T> T getPlugin(Class<T> pluginClass, String defaultClassName, + T defaultInstance) { + assert property.type() == ConnectionProperty.Type.PLUGIN; + return get_(pluginConverter(pluginClass, defaultInstance), + defaultClassName); + } + } + + /** Callback to parse a property from string to its native type. */ + public interface Converter<T> { + T apply(ConnectionProperty connectionProperty, String s); + } + + public static final Converter<Boolean> BOOLEAN_CONVERTER = + new Converter<Boolean>() { + public Boolean apply(ConnectionProperty connectionProperty, String s) { + if (s == null) { + throw new RuntimeException("Required property '" + + connectionProperty.camelName() + "' not specified"); + } + return Boolean.parseBoolean(s); + } + }; + + public static final Converter<String> IDENTITY_CONVERTER = + new Converter<String>() { + public String apply(ConnectionProperty connectionProperty, String s) { + return s; + } + }; + + public static <E extends Enum> Converter<E> enumConverter( + final Class<E> enumClass) { + return new Converter<E>() { + public E apply(ConnectionProperty connectionProperty, String s) { + if (s == null) { + throw new RuntimeException("Required property '" + + connectionProperty.camelName() + "' not specified"); + } + try { + return (E) Enum.valueOf(enumClass, s); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Property '" + s + "' not valid for enum " + + enumClass.getName()); + } + } + }; + } + + public static <T> Converter<T> pluginConverter(final Class<T> pluginClass, + final T defaultInstance) { + return new Converter<T>() { + public T apply(ConnectionProperty connectionProperty, String s) { + if (s == null) { + if (defaultInstance != null) { + return defaultInstance; + } + if (!connectionProperty.required()) { + return null; + } + throw new RuntimeException("Required property '" + + connectionProperty.camelName() + "' not specified"); + } + return AvaticaUtils.instantiatePlugin(pluginClass, s); + } + }; + } +} + +// End ConnectionConfigImpl.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionPropertiesImpl.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionPropertiesImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionPropertiesImpl.java new file mode 100644 index 0000000..c147ecc --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionPropertiesImpl.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica; + +import org.apache.calcite.avatica.proto.Common; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.protobuf.Descriptors.FieldDescriptor; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +/** Concrete implementation of {@link Meta.ConnectionProperties}. Provides additional state + * tracking to enable {@code RemoteMeta} to lazily push changes up to a query server. + * + * <p>{@code Meta} instances should probably hold authority on the {@code isDirty} + * flag because {@code AvaticaConnection} instances have no way of knowing if they're local or + * remote. + */ +public class ConnectionPropertiesImpl implements Meta.ConnectionProperties { + private static final FieldDescriptor CATALOG_DESCRIPTOR = Common.ConnectionProperties + .getDescriptor().findFieldByNumber(Common.ConnectionProperties.CATALOG_FIELD_NUMBER); + private static final FieldDescriptor SCHEMA_DESCRIPTOR = Common.ConnectionProperties + .getDescriptor().findFieldByNumber(Common.ConnectionProperties.SCHEMA_FIELD_NUMBER); + private static final FieldDescriptor TRANSACTION_ISOLATION_DESCRIPTOR = Common + .ConnectionProperties.getDescriptor().findFieldByNumber( + Common.ConnectionProperties.TRANSACTION_ISOLATION_FIELD_NUMBER); + + private boolean isDirty = false; + private Boolean autoCommit; + private Boolean readOnly; + private Integer transactionIsolation; + private String catalog; + private String schema; + + // TODO: replace with Meta.ConnectionProperties$EMPTY instance? + public ConnectionPropertiesImpl() {} + + public ConnectionPropertiesImpl(Connection conn) throws SQLException { + this(conn.getAutoCommit(), conn.isReadOnly(), conn.getTransactionIsolation(), + conn.getCatalog(), conn.getSchema()); + } + + @JsonCreator + public ConnectionPropertiesImpl( + @JsonProperty("autoCommit") Boolean autoCommit, + @JsonProperty("readOnly") Boolean readOnly, + @JsonProperty("transactionIsolation") Integer transactionIsolation, + @JsonProperty("catalog") String catalog, + @JsonProperty("schema") String schema) { + this.autoCommit = autoCommit; + this.readOnly = readOnly; + this.transactionIsolation = transactionIsolation; + this.catalog = catalog; + this.schema = schema; + } + + public ConnectionPropertiesImpl setDirty(boolean val) { + this.isDirty = val; + return this; + } + + public boolean isDirty() { + return this.isDirty; + } + + @Override public boolean isEmpty() { + return autoCommit == null && readOnly == null && transactionIsolation == null + && catalog == null && schema == null; + } + + /** Overwrites fields in {@code this} with any non-null fields in {@code that}. Sets + * {@code isDirty} if any fields are changed. + * + * @return {@code this} + */ + @Override public ConnectionPropertiesImpl merge(Meta.ConnectionProperties that) { + if (this == that) { + return this; + } + if (that.isAutoCommit() != null && this.autoCommit != that.isAutoCommit()) { + this.autoCommit = that.isAutoCommit(); + this.isDirty = true; + } + if (that.isReadOnly() != null && this.readOnly != that.isReadOnly()) { + this.readOnly = that.isReadOnly(); + this.isDirty = true; + } + if (that.getTransactionIsolation() != null + && !that.getTransactionIsolation().equals(this.transactionIsolation)) { + this.transactionIsolation = that.getTransactionIsolation(); + this.isDirty = true; + } + if (that.getCatalog() != null && !that.getCatalog().equalsIgnoreCase(this.catalog)) { + this.catalog = that.getCatalog(); + this.isDirty = true; + } + if (that.getSchema() != null && !that.getSchema().equalsIgnoreCase(this.schema)) { + this.schema = that.getSchema(); + this.isDirty = true; + } + return this; + } + + /** Sets {@code autoCommit} status and flag as dirty. + * + * @return {@code this} + */ + @Override public Meta.ConnectionProperties setAutoCommit(boolean val) { + this.autoCommit = val; + this.isDirty = true; + return this; + } + + @Override public Boolean isAutoCommit() { + return this.autoCommit; + } + + /** Sets {@code readOnly} status and flag as dirty. + * + * @return {@code this} + */ + @Override public Meta.ConnectionProperties setReadOnly(boolean val) { + this.readOnly = val; + this.isDirty = true; + return this; + } + + @Override public Boolean isReadOnly() { + return this.readOnly; + } + + /** Sets {@code transactionIsolation} status and flag as dirty. + * + * @return {@code this} + */ + @Override public Meta.ConnectionProperties setTransactionIsolation(int val) { + this.transactionIsolation = val; + this.isDirty = true; + return this; + } + + public Integer getTransactionIsolation() { + return this.transactionIsolation; + } + + /** Sets {@code catalog} and flag as dirty. + * + * @return {@code this} + */ + @Override public Meta.ConnectionProperties setCatalog(String val) { + this.catalog = val; + this.isDirty = true; + return this; + } + + @Override public String getCatalog() { + return this.catalog; + } + + /** Sets {@code schema} and flag as dirty. + * + * @return {@code this} + */ + @Override public Meta.ConnectionProperties setSchema(String val) { + this.schema = val; + this.isDirty = true; + return this; + } + + public String getSchema() { + return this.schema; + } + + @Override public int hashCode() { + return Objects.hash(autoCommit, catalog, isDirty, readOnly, schema, + transactionIsolation); + } + + @Override public boolean equals(Object o) { + return o == this + || o instanceof ConnectionPropertiesImpl + && Objects.equals(autoCommit, ((ConnectionPropertiesImpl) o).autoCommit) + && Objects.equals(catalog, ((ConnectionPropertiesImpl) o).catalog) + && isDirty == ((ConnectionPropertiesImpl) o).isDirty + && Objects.equals(readOnly, ((ConnectionPropertiesImpl) o).readOnly) + && Objects.equals(schema, ((ConnectionPropertiesImpl) o).schema) + && Objects.equals(transactionIsolation, + ((ConnectionPropertiesImpl) o).transactionIsolation); + } + + public Common.ConnectionProperties toProto() { + Common.ConnectionProperties.Builder builder = Common.ConnectionProperties.newBuilder(); + + if (null != autoCommit) { + builder.setHasAutoCommit(true); + builder.setAutoCommit(autoCommit.booleanValue()); + } else { + // Be explicit to avoid default value confusion + builder.setHasAutoCommit(false); + } + + if (null != catalog) { + builder.setCatalog(catalog); + } + + builder.setIsDirty(isDirty); + + if (null != readOnly) { + builder.setHasReadOnly(true); + builder.setReadOnly(readOnly.booleanValue()); + } else { + // Be explicit to avoid default value confusion + builder.setHasReadOnly(false); + } + + if (null != schema) { + builder.setSchema(schema); + } + + if (null != transactionIsolation) { + builder.setTransactionIsolation(transactionIsolation.intValue()); + } + + return builder.build(); + } + + public static ConnectionPropertiesImpl fromProto(Common.ConnectionProperties proto) { + String catalog = null; + if (proto.hasField(CATALOG_DESCRIPTOR)) { + catalog = proto.getCatalog(); + } + + String schema = null; + if (proto.hasField(SCHEMA_DESCRIPTOR)) { + schema = proto.getSchema(); + } + + Boolean autoCommit = null; + if (proto.getHasAutoCommit()) { + autoCommit = Boolean.valueOf(proto.getAutoCommit()); + } + + Boolean readOnly = null; + if (proto.getHasReadOnly()) { + readOnly = Boolean.valueOf(proto.getReadOnly()); + } + + Integer transactionIsolation = null; + if (proto.hasField(TRANSACTION_ISOLATION_DESCRIPTOR)) { + transactionIsolation = Integer.valueOf(proto.getTransactionIsolation()); + } + + ConnectionPropertiesImpl impl = new ConnectionPropertiesImpl(autoCommit, readOnly, + transactionIsolation, catalog, schema); + + impl.setDirty(proto.getIsDirty()); + + return impl; + } +} + +// End ConnectionPropertiesImpl.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionProperty.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionProperty.java b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionProperty.java new file mode 100644 index 0000000..d380d80 --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/ConnectionProperty.java @@ -0,0 +1,69 @@ +/* + * 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 java.util.Properties; + +/** + * Definition of a property that may be specified on the JDBC connect string. + * {@link BuiltInConnectionProperty} enumerates built-in properties, but + * there may be others; the list is not closed. + */ +public interface ConnectionProperty { + /** The name of this property. (E.g. "MATERIALIZATIONS_ENABLED".) */ + String name(); + + /** The name of this property in camel-case. + * (E.g. "materializationsEnabled".) */ + String camelName(); + + /** Returns the default value of this property. The type must match its data + * type. */ + Object defaultValue(); + + /** Returns the data type of this property. */ + Type type(); + + /** Wraps this property with a properties object from which its value can be + * obtained when needed. */ + ConnectionConfigImpl.PropEnv wrap(Properties properties); + + /** Whether the property is mandatory. */ + boolean required(); + + /** Data type of property. */ + enum Type { + BOOLEAN, + STRING, + ENUM, + PLUGIN; + + public boolean valid(Object defaultValue) { + switch (this) { + case BOOLEAN: + return defaultValue instanceof Boolean; + case STRING: + case PLUGIN: + return defaultValue instanceof String; + default: + return defaultValue instanceof Enum; + } + } + } +} + +// End ConnectionProperty.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/DriverVersion.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/DriverVersion.java b/avatica/core/src/main/java/org/apache/calcite/avatica/DriverVersion.java new file mode 100644 index 0000000..15c966a --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/DriverVersion.java @@ -0,0 +1,149 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Driver version information. + * + * <p>Each driver implementation must provide an instance of this class, in + * order to implement {@link UnregisteredDriver#createDriverVersion()}.</p> + * + * <p>There are two typical ways for a driver to instantiate its version + * information:</p> + * + * <ul> + * + * <li>A driver might create a subclass in a with a constructor that provides + * all of the arguments for the base class. The instance is held in a separate + * file, so that that version information can be generated.</li> + * + * <li>A driver might store the version information in a .properties file and + * load it using {@link #load}.</li> + * + * </ul> + */ +public class DriverVersion { + public final int majorVersion; + public final int minorVersion; + public final String name; + public final String versionString; + public final String productName; + public final String productVersion; + public final boolean jdbcCompliant; + public final int databaseMajorVersion; + public final int databaseMinorVersion; + + /** Creates a DriverVersion. */ + public DriverVersion( + String name, + String versionString, + String productName, + String productVersion, + boolean jdbcCompliant, + int majorVersion, + int minorVersion, + int databaseMajorVersion, + int databaseMinorVersion) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.name = name; + this.versionString = versionString; + this.productName = productName; + this.productVersion = productVersion; + this.jdbcCompliant = jdbcCompliant; + this.databaseMajorVersion = databaseMajorVersion; + this.databaseMinorVersion = databaseMinorVersion; + } + + /** Loads a driver version from a properties file, read from the classpath. + * The arguments provide defaults if the properties cannot be loaded. + * + * @param driverClass Class of driver; used to find resource + * @param resourceName Name of resource file + * @param driverName Fallback name of driver + * @param driverVersion Fallback version of driver + * @param productName Fallback product name + * @param productVersion Fallback product version + * @return A populated driver version object, never null + */ + public static DriverVersion load( + Class<? extends UnregisteredDriver> driverClass, + String resourceName, + String driverName, + String driverVersion, + String productName, + String productVersion) { + boolean jdbcCompliant = true; + int majorVersion = 0; + int minorVersion = 0; + int databaseMajorVersion = 0; + int databaseMinorVersion = 0; + try { + final InputStream inStream = + driverClass.getClassLoader().getResourceAsStream(resourceName); + if (inStream != null) { + final Properties properties = new Properties(); + properties.load(inStream); + driverName = properties.getProperty("driver.name"); + driverVersion = properties.getProperty("driver.version"); + productName = properties.getProperty("product.name"); + productVersion = properties.getProperty("product.version"); + jdbcCompliant = + Boolean.valueOf(properties.getProperty("jdbc.compliant")); + String[] s = driverVersion.replaceAll("-.*$", "").split("\\."); + final int major = Integer.valueOf(s[0]); + final int minor = Integer.valueOf(s[1]); + try { + majorVersion = + Integer.valueOf(properties.getProperty("driver.version.major")); + } catch (NumberFormatException e) { + majorVersion = major; + } + try { + minorVersion = + Integer.valueOf(properties.getProperty("driver.version.minor")); + } catch (NumberFormatException e) { + minorVersion = minor; + } + try { + databaseMajorVersion = + Integer.valueOf(properties.getProperty("database.version.major")); + } catch (NumberFormatException e) { + databaseMajorVersion = major; + } + try { + databaseMinorVersion = + Integer.valueOf(properties.getProperty("database.version.minor")); + } catch (NumberFormatException e) { + databaseMinorVersion = minor; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return new DriverVersion( + driverName, driverVersion, productName, productVersion, + jdbcCompliant, majorVersion, minorVersion, databaseMajorVersion, + databaseMinorVersion); + } +} + +// End DriverVersion.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/Handler.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/Handler.java b/avatica/core/src/main/java/org/apache/calcite/avatica/Handler.java new file mode 100644 index 0000000..831e66d --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/Handler.java @@ -0,0 +1,87 @@ +/* + * 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 java.sql.SQLException; + +/** + * Called at various points in the JDBC lifecycle. + * + * <p>Most drivers will use {@link HandlerImpl}, which provides no-op + * implementations of all methods. You only need to override methods if you + * need to achieve special effects.</p> + */ +public interface Handler { + /** Called by container when a connection is being created. + * + * <p>If the implementation of this method throws, the connection + * will not be created.</p> + * + * @param connection Connection + * @throws SQLException on error + */ + void onConnectionInit(AvaticaConnection connection) throws SQLException; + + /** Called by container when a connection is being closed. + * + * <p>If the implementation of this method throws, the call to + * {@link java.sql.Connection#close} that triggered this method will throw an + * exception, but the connection will still be marked closed.</p> + * + * @param connection Connection + */ + void onConnectionClose(AvaticaConnection connection); + + /** Called by container when a statement is being executed. + * + * <p>If the session would like the statement results stored in a temporary + * table, {@code resultSink} is not null. + * The provider must call its {@link ResultSink#toBeCompleted} + * method at some point during execution (not necessarily before the call to + * this method returns).</p> + * + * @param statement Statement + * @param resultSink Place to put result of query. Null if container does not + * want results stored to a temporary table + * @throws RuntimeException on error + */ + void onStatementExecute( + AvaticaStatement statement, + ResultSink resultSink); + + /** Called by container when a statement is being closed. + * + * <p>This method is called after marking the statement closed, and after + * closing any open {@link java.sql.ResultSet} objects.</p> + * + * <p>If the implementation of this method throws, the call to + * {@link java.sql.Statement#close} that triggered this method will throw an + * exception, but the statement will still be marked closed. + * + * @param statement Statement + * @throws RuntimeException on error + */ + void onStatementClose(AvaticaStatement statement); + + /** Handler for temporary tables. */ + interface ResultSink { + /** Registers a temporary table. */ + void toBeCompleted(); + } +} + +// End Handler.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/HandlerImpl.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/HandlerImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/HandlerImpl.java new file mode 100644 index 0000000..c2e6c1a --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/HandlerImpl.java @@ -0,0 +1,47 @@ +/* + * 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 java.sql.SQLException; + +/** + * Implementation of {@link Handler} that does nothing for each callback. + * It is recommended implementations of {@code Handler} use this as a base + * class, to ensure forward compatibility. + */ +public class HandlerImpl implements Handler { + public void onConnectionInit(AvaticaConnection connection) + throws SQLException { + // nothing + } + + public void onConnectionClose(AvaticaConnection connection) { + // nothing + } + + public void onStatementExecute( + AvaticaStatement statement, + ResultSink resultSink) { + // nothing + } + + public void onStatementClose(AvaticaStatement statement) { + // nothing + } +} + +// End HandlerImpl.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/Helper.java ---------------------------------------------------------------------- diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/Helper.java b/avatica/core/src/main/java/org/apache/calcite/avatica/Helper.java new file mode 100644 index 0000000..27c6056 --- /dev/null +++ b/avatica/core/src/main/java/org/apache/calcite/avatica/Helper.java @@ -0,0 +1,76 @@ +/* + * 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 java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +/** + * Utility methods, mainly concerning error-handling. + */ +public class Helper { + public static final Helper INSTANCE = new Helper(); + + private Helper() { + } + + public RuntimeException todo() { + return new RuntimeException("todo: implement this method"); + } + + public RuntimeException wrap(String message, Exception e) { + return new RuntimeException(message, e); + } + + public SQLException createException(String message, Exception e) { + return createException(message, null, e); + } + + public SQLException createException(String message, String sql, Exception e) { + if (e instanceof AvaticaClientRuntimeException) { + // The AvaticaClientRuntimeException contains extra information about what/why + // the exception was thrown that we can pass back to the user. + AvaticaClientRuntimeException rte = (AvaticaClientRuntimeException) e; + String serverAddress = null; + if (null != rte.getRpcMetadata()) { + serverAddress = rte.getRpcMetadata().serverAddress; + } + return new AvaticaSqlException(message, rte.getSqlState(), rte.getErrorCode(), + rte.getServerExceptions(), serverAddress); + } + return new SQLException(message, e); + } + + public SQLException createException(String message) { + return new SQLException(message); + } + + public SQLException toSQLException(SQLException exception) { + return exception; + } + + public SQLException unsupported() { + return new SQLFeatureNotSupportedException(); + } + + public SQLClientInfoException clientInfo() { + return new SQLClientInfoException(); + } +} + +// End Helper.java
