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&lt;Double&gt;}.
+   */
+  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&lt;EOF&gt;".
+   *
+   * @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&lt;EOF&gt;"
+   *
+   * @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}&lt;String,
+   * String&gt;</code>.
+   *
+   * <p>This is necessary because {@link Properties} is a dinosaur class. It
+   * ought to extend <code>Map&lt;String,String&gt;</code>, but instead
+   * extends <code>{@link java.util.Hashtable}&lt;Object,Object&gt;</code>.
+   *
+   * <p>Typical usage, to iterate over a {@link Properties}:
+   *
+   * <blockquote>
+   * <code>
+   * Properties properties;<br>
+   * for (Map.Entry&lt;String, String&gt; 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