You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by el...@apache.org on 2016/03/07 19:28:21 UTC
[43/59] [partial] calcite git commit: [CALCITE-1078] Detach avatica
from the core calcite Maven project
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/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