You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by im...@apache.org on 2021/10/05 22:38:43 UTC

[asterixdb-clients] 02/02: [NO ISSUE] Import JDBC driver

This is an automated email from the ASF dual-hosted git repository.

imaxon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb-clients.git

commit 0cb72193a47fe8f1b0fb45c4d150b06bfcd85a1d
Author: Ian Maxon <ia...@maxons.email>
AuthorDate: Fri Oct 1 15:55:37 2021 -0700

    [NO ISSUE] Import JDBC driver
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    - import code donation from
      http://incubator.apache.org/ip-clearance/asterixdb-jdbc-driver
    
    Change-Id: I0cab464afd70be5cca4b198b7571c012c32a8c5e
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb-clients/+/13483
    Reviewed-by: Michael Blow <mb...@apache.org>
    Tested-by: Michael Blow <mb...@apache.org>
---
 asterixdb-jdbc/LICENSE                             |  201 +++
 asterixdb-jdbc/NOTICE                              |    8 +
 asterixdb-jdbc/asterix-jdbc-core/pom.xml           |   72 +
 .../org/apache/asterix/jdbc/core/ADBColumn.java    |   54 +
 .../apache/asterix/jdbc/core/ADBConnection.java    |  556 +++++++
 .../asterix/jdbc/core/ADBDatabaseMetaData.java     | 1038 +++++++++++++
 .../org/apache/asterix/jdbc/core/ADBDatatype.java  |  177 +++
 .../apache/asterix/jdbc/core/ADBDriverBase.java    |  255 ++++
 .../apache/asterix/jdbc/core/ADBDriverContext.java |   86 ++
 .../asterix/jdbc/core/ADBDriverProperty.java       |   71 +
 .../apache/asterix/jdbc/core/ADBErrorReporter.java |  207 +++
 .../apache/asterix/jdbc/core/ADBMetaStatement.java |  350 +++++
 .../asterix/jdbc/core/ADBParameterMetaData.java    |   86 ++
 .../asterix/jdbc/core/ADBPreparedStatement.java    |  496 +++++++
 .../asterix/jdbc/core/ADBProductVersion.java       |   98 ++
 .../org/apache/asterix/jdbc/core/ADBProtocol.java  |  641 ++++++++
 .../org/apache/asterix/jdbc/core/ADBResultSet.java | 1536 ++++++++++++++++++++
 .../asterix/jdbc/core/ADBResultSetMetaData.java    |  194 +++
 .../org/apache/asterix/jdbc/core/ADBRowStore.java  | 1191 +++++++++++++++
 .../org/apache/asterix/jdbc/core/ADBStatement.java |  846 +++++++++++
 .../asterix/jdbc/core/ADBWrapperSupport.java       |   41 +
 asterixdb-jdbc/asterix-jdbc-driver/pom.xml         |  137 ++
 .../main/java/org/apache/asterix/jdbc/Driver.java  |   38 +
 .../resources/META-INF/services/java.sql.Driver    |   19 +
 24 files changed, 8398 insertions(+)

diff --git a/asterixdb-jdbc/LICENSE b/asterixdb-jdbc/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/asterixdb-jdbc/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/asterixdb-jdbc/NOTICE b/asterixdb-jdbc/NOTICE
new file mode 100644
index 0000000..0f871c8
--- /dev/null
+++ b/asterixdb-jdbc/NOTICE
@@ -0,0 +1,8 @@
+Apache AsterixDB JDBC Driver
+Copyright 2021 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+The Initial Developer of the driver software is Couchbase, Inc.
+Copyright 2021 Couchbase, Inc.
diff --git a/asterixdb-jdbc/asterix-jdbc-core/pom.xml b/asterixdb-jdbc/asterix-jdbc-core/pom.xml
new file mode 100644
index 0000000..87fb7c8
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/pom.xml
@@ -0,0 +1,72 @@
+<!--
+ ! 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.
+ !-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>apache-asterixdb</artifactId>
+    <groupId>org.apache.asterix</groupId>
+    <version>0.9.7-SNAPSHOT</version>
+  </parent>
+  <artifactId>asterix-jdbc-core</artifactId>
+
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+      <comments>A business-friendly OSS license</comments>
+    </license>
+  </licenses>
+
+  <properties>
+    <root.dir>${basedir}/..</root.dir>
+    <source.jdk.version>1.8</source.jdk.version>
+    <target.jdk.version>1.8</target.jdk.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBColumn.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBColumn.java
new file mode 100644
index 0000000..eb2de01
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBColumn.java
@@ -0,0 +1,54 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.util.Objects;
+
+final class ADBColumn {
+
+    private final String name;
+
+    private final ADBDatatype type;
+
+    private final boolean optional;
+
+    ADBColumn(String name, ADBDatatype type, boolean optional) {
+        this.name = Objects.requireNonNull(name);
+        this.type = Objects.requireNonNull(type);
+        this.optional = optional || type.isNullOrMissing() || type == ADBDatatype.ANY;
+    }
+
+    String getName() {
+        return name;
+    }
+
+    ADBDatatype getType() {
+        return type;
+    }
+
+    boolean isOptional() {
+        return optional;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s:%s%s", name, type.getTypeName(), optional ? "?" : "");
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBConnection.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBConnection.java
new file mode 100644
index 0000000..46e38bc
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBConnection.java
@@ -0,0 +1,556 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLPermission;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ADBConnection extends ADBWrapperSupport implements Connection {
+
+    final ADBProtocol protocol;
+
+    final String url;
+
+    final String databaseVersion;
+
+    private final AtomicBoolean closed;
+
+    private final ConcurrentLinkedQueue<ADBStatement> statements;
+
+    private volatile SQLWarning warning;
+
+    private volatile ADBMetaStatement metaStatement;
+
+    volatile String catalog;
+
+    volatile String schema;
+
+    // Lifecycle
+
+    protected ADBConnection(ADBProtocol protocol, String url, String databaseVersion, String catalog, String schema,
+            SQLWarning connectWarning) {
+        this.url = Objects.requireNonNull(url);
+        this.protocol = Objects.requireNonNull(protocol);
+        this.databaseVersion = databaseVersion;
+        this.statements = new ConcurrentLinkedQueue<>();
+        this.warning = connectWarning;
+        this.catalog = catalog;
+        this.schema = schema;
+        this.closed = new AtomicBoolean(false);
+    }
+
+    @Override
+    public void close() throws SQLException {
+        closeImpl(null);
+    }
+
+    @Override
+    public void abort(Executor executor) throws SQLException {
+        if (executor == null) {
+            throw getErrorReporter().errorParameterValueNotSupported("executor");
+        }
+        SecurityManager sec = System.getSecurityManager();
+        if (sec != null) {
+            sec.checkPermission(new SQLPermission("callAbort"));
+        }
+        closeImpl(executor);
+    }
+
+    void closeImpl(Executor executor) throws SQLException {
+        boolean wasClosed = closed.getAndSet(true);
+        if (wasClosed) {
+            return;
+        }
+        if (executor == null) {
+            closeStatementsAndProtocol();
+        } else {
+            executor.execute(() -> {
+                try {
+                    closeStatementsAndProtocol();
+                } catch (SQLException e) {
+                    if (getLogger().isLoggable(Level.FINE)) {
+                        getLogger().log(Level.FINE, e.getMessage(), e);
+                    }
+                }
+            });
+        }
+    }
+
+    private void closeStatementsAndProtocol() throws SQLException {
+        SQLException err = null;
+        try {
+            closeRegisteredStatements();
+        } catch (SQLException e) {
+            err = e;
+        }
+        try {
+            protocol.close();
+        } catch (SQLException e) {
+            if (err != null) {
+                e.addSuppressed(err);
+            }
+            err = e;
+        }
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closed.get();
+    }
+
+    private void checkClosed() throws SQLException {
+        if (isClosed()) {
+            throw getErrorReporter().errorObjectClosed(Connection.class);
+        }
+    }
+
+    // Connectivity
+
+    @Override
+    public boolean isValid(int timeoutSeconds) throws SQLException {
+        if (isClosed()) {
+            return false;
+        }
+        if (timeoutSeconds < 0) {
+            throw getErrorReporter().errorParameterValueNotSupported("timeoutSeconds");
+        }
+        return protocol.ping(timeoutSeconds);
+    }
+
+    @Override
+    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "setNetworkTimeout");
+    }
+
+    @Override
+    public int getNetworkTimeout() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "getNetworkTimeout");
+    }
+
+    // Metadata
+
+    @Override
+    public DatabaseMetaData getMetaData() throws SQLException {
+        checkClosed();
+        ADBMetaStatement metaStatement = getOrCreateMetaStatement();
+        return createDatabaseMetaData(metaStatement);
+    }
+
+    private ADBMetaStatement getOrCreateMetaStatement() {
+        ADBMetaStatement stmt = metaStatement;
+        if (stmt == null) {
+            synchronized (this) {
+                stmt = metaStatement;
+                if (stmt == null) {
+                    stmt = createMetaStatement();
+                    registerStatement(stmt);
+                    metaStatement = stmt;
+                }
+            }
+        }
+        return stmt;
+    }
+
+    protected ADBMetaStatement createMetaStatement() {
+        return new ADBMetaStatement(this);
+    }
+
+    protected ADBDatabaseMetaData createDatabaseMetaData(ADBMetaStatement metaStatement) {
+        return new ADBDatabaseMetaData(metaStatement, databaseVersion);
+    }
+
+    // Statement construction
+
+    @Override
+    public Statement createStatement() throws SQLException {
+        checkClosed();
+        return createStatementImpl();
+    }
+
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+        return createStatement(resultSetType, resultSetConcurrency, getHoldability());
+    }
+
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+            throws SQLException {
+        checkClosed();
+        checkResultSetConfig(resultSetType, resultSetConcurrency, resultSetHoldability);
+        return createStatementImpl();
+    }
+
+    private void checkResultSetConfig(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+            throws SQLException {
+        boolean ok = resultSetType == ResultSet.TYPE_FORWARD_ONLY && resultSetConcurrency == ResultSet.CONCUR_READ_ONLY;
+        if (!ok) {
+            throw getErrorReporter().errorParameterValueNotSupported("resultSetType/resultSetConcurrency");
+        }
+        if (resultSetHoldability != ADBResultSet.RESULT_SET_HOLDABILITY) {
+            if (getLogger().isLoggable(Level.FINE)) {
+                getLogger().log(Level.FINE,
+                        getErrorReporter().warningParameterValueNotSupported("ResultSetHoldability"));
+            }
+        }
+    }
+
+    private ADBStatement createStatementImpl() {
+        ADBStatement stmt = new ADBStatement(this, catalog, schema);
+        registerStatement(stmt);
+        return stmt;
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql) throws SQLException {
+        checkClosed();
+        return prepareStatementImpl(sql);
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+            throws SQLException {
+        return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
+            int resultSetHoldability) throws SQLException {
+        checkClosed();
+        checkResultSetConfig(resultSetType, resultSetConcurrency, resultSetHoldability);
+        return prepareStatementImpl(sql);
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
+    }
+
+    @Override
+    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
+    }
+
+    private ADBPreparedStatement prepareStatementImpl(String sql) throws SQLException {
+        ADBPreparedStatement stmt = new ADBPreparedStatement(this, sql, catalog, schema);
+        registerStatement(stmt);
+        return stmt;
+    }
+
+    @Override
+    public CallableStatement prepareCall(String sql) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
+    }
+
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
+    }
+
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+            int resultSetHoldability) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
+    }
+
+    @Override
+    public String nativeSQL(String sql) throws SQLException {
+        checkClosed();
+        return sql;
+    }
+
+    @Override
+    public String getCatalog() throws SQLException {
+        checkClosed();
+        return catalog;
+    }
+
+    @Override
+    public void setCatalog(String catalog) throws SQLException {
+        checkClosed();
+        this.catalog = catalog;
+    }
+
+    @Override
+    public String getSchema() throws SQLException {
+        checkClosed();
+        return schema;
+    }
+
+    @Override
+    public void setSchema(String schema) throws SQLException {
+        checkClosed();
+        this.schema = schema;
+    }
+
+    // Statement lifecycle
+
+    private void registerStatement(ADBStatement stmt) {
+        statements.add(Objects.requireNonNull(stmt));
+    }
+
+    void deregisterStatement(ADBStatement stmt) {
+        statements.remove(Objects.requireNonNull(stmt));
+    }
+
+    private void closeRegisteredStatements() throws SQLException {
+        SQLException err = null;
+        ADBStatement statement;
+        while ((statement = statements.poll()) != null) {
+            try {
+                statement.closeImpl(true, false);
+            } catch (SQLException e) {
+                if (err != null) {
+                    e.addSuppressed(err);
+                }
+                err = e;
+            }
+        }
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    // Transaction control
+
+    @Override
+    public int getTransactionIsolation() throws SQLException {
+        checkClosed();
+        return Connection.TRANSACTION_READ_COMMITTED;
+    }
+
+    @Override
+    public void setTransactionIsolation(int level) throws SQLException {
+        checkClosed();
+        switch (level) {
+            case Connection.TRANSACTION_READ_COMMITTED:
+                break;
+            case Connection.TRANSACTION_READ_UNCOMMITTED:
+            case Connection.TRANSACTION_REPEATABLE_READ:
+            case Connection.TRANSACTION_SERIALIZABLE:
+                if (getLogger().isLoggable(Level.FINE)) {
+                    getLogger().log(Level.FINE,
+                            getErrorReporter().warningParameterValueNotSupported("TransactionIsolationLevel"));
+                }
+                break;
+            default:
+                throw getErrorReporter().errorParameterValueNotSupported("TransactionIsolationLevel");
+        }
+    }
+
+    @Override
+    public int getHoldability() throws SQLException {
+        checkClosed();
+        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    @Override
+    public void setHoldability(int holdability) throws SQLException {
+        checkClosed();
+        switch (holdability) {
+            case ResultSet.HOLD_CURSORS_OVER_COMMIT:
+                break;
+            case ResultSet.CLOSE_CURSORS_AT_COMMIT:
+                if (getLogger().isLoggable(Level.FINE)) {
+                    getLogger().log(Level.FINE, getErrorReporter().warningParameterValueNotSupported("Holdability"));
+                }
+                break;
+            default:
+                throw getErrorReporter().errorParameterValueNotSupported("Holdability");
+        }
+    }
+
+    @Override
+    public boolean getAutoCommit() throws SQLException {
+        checkClosed();
+        return true;
+    }
+
+    @Override
+    public void setAutoCommit(boolean autoCommit) throws SQLException {
+        checkClosed();
+    }
+
+    @Override
+    public void commit() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("AutoCommit");
+    }
+
+    @Override
+    public void rollback() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("AutoCommit");
+    }
+
+    @Override
+    public Savepoint setSavepoint() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "setSavepoint");
+    }
+
+    @Override
+    public Savepoint setSavepoint(String name) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "setSavepoint");
+    }
+
+    @Override
+    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "releaseSavepoint");
+    }
+
+    @Override
+    public void rollback(Savepoint savepoint) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "rollback");
+    }
+
+    // Value construction
+
+    @Override
+    public Clob createClob() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createClob");
+    }
+
+    @Override
+    public Blob createBlob() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createBlob");
+    }
+
+    @Override
+    public NClob createNClob() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createNClob");
+    }
+
+    @Override
+    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createArrayOf");
+    }
+
+    @Override
+    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createStruct");
+    }
+
+    @Override
+    public SQLXML createSQLXML() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "createSQLXML");
+    }
+
+    @Override
+    public Map<String, Class<?>> getTypeMap() throws SQLException {
+        checkClosed();
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Connection.class, "setTypeMap");
+    }
+
+    // Unsupported hints (ignored)
+
+    @Override
+    public boolean isReadOnly() throws SQLException {
+        checkClosed();
+        return false;
+    }
+
+    @Override
+    public void setReadOnly(boolean readOnly) throws SQLException {
+        checkClosed();
+    }
+
+    // Errors and warnings
+
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        checkClosed();
+        return warning;
+    }
+
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkClosed();
+        warning = null;
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return protocol.driverContext.errorReporter;
+    }
+
+    protected Logger getLogger() {
+        return protocol.driverContext.logger;
+    }
+
+    // Miscellaneous unsupported features (error is raised)
+
+    @Override
+    public String getClientInfo(String name) throws SQLException {
+        checkClosed();
+        return null;
+    }
+
+    @Override
+    public Properties getClientInfo() throws SQLException {
+        checkClosed();
+        return new Properties();
+    }
+
+    @Override
+    public void setClientInfo(Properties properties) throws SQLClientInfoException {
+        throw getErrorReporter().errorClientInfoMethodNotSupported(Connection.class, "setClientInfo");
+    }
+
+    @Override
+    public void setClientInfo(String name, String value) throws SQLClientInfoException {
+        throw getErrorReporter().errorClientInfoMethodNotSupported(Connection.class, "setClientInfo");
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatabaseMetaData.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatabaseMetaData.java
new file mode 100644
index 0000000..a090498
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatabaseMetaData.java
@@ -0,0 +1,1038 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.RowIdLifetime;
+import java.sql.SQLException;
+import java.util.Objects;
+
+final class ADBDatabaseMetaData extends ADBWrapperSupport implements DatabaseMetaData {
+
+    /*
+     * See org.apache.asterix.metadata.utils.MetadataConstants.METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8
+     */
+    private static final int METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8 = 251;
+
+    private final ADBMetaStatement metaStatement;
+
+    private final String databaseVersionText;
+
+    private volatile ADBProductVersion databaseVersion;
+
+    ADBDatabaseMetaData(ADBMetaStatement metaStatement, String databaseVersionText) {
+        this.metaStatement = Objects.requireNonNull(metaStatement);
+        this.databaseVersionText = databaseVersionText;
+    }
+
+    // Driver name and version
+
+    @Override
+    public String getDriverName() {
+        return metaStatement.connection.protocol.driverContext.driverVersion.productName;
+    }
+
+    @Override
+    public String getDriverVersion() {
+        return metaStatement.connection.protocol.driverContext.driverVersion.productVersion;
+    }
+
+    @Override
+    public int getDriverMajorVersion() {
+        return metaStatement.connection.protocol.driverContext.driverVersion.majorVersion;
+    }
+
+    @Override
+    public int getDriverMinorVersion() {
+        return metaStatement.connection.protocol.driverContext.driverVersion.minorVersion;
+    }
+
+    @Override
+    public int getJDBCMajorVersion() {
+        return ADBDriverBase.JDBC_MAJOR_VERSION;
+    }
+
+    @Override
+    public int getJDBCMinorVersion() {
+        return ADBDriverBase.JDBC_MINOR_VERSION;
+    }
+
+    // Database name and version
+
+    @Override
+    public String getDatabaseProductName() {
+        return getDatabaseVersion().productName;
+    }
+
+    @Override
+    public String getDatabaseProductVersion() {
+        return getDatabaseVersion().productVersion;
+    }
+
+    @Override
+    public int getDatabaseMajorVersion() {
+        return getDatabaseVersion().majorVersion;
+    }
+
+    @Override
+    public int getDatabaseMinorVersion() {
+        return getDatabaseVersion().minorVersion;
+    }
+
+    private ADBProductVersion getDatabaseVersion() {
+        ADBProductVersion result = databaseVersion;
+        if (result == null) {
+            databaseVersion = result = ADBProductVersion.parseDatabaseVersion(databaseVersionText);
+        }
+        return result;
+    }
+
+    // Database objects
+
+    // Catalogs and schemas
+
+    @Override
+    public ADBResultSet getCatalogs() throws SQLException {
+        return metaStatement.executeGetCatalogsQuery();
+    }
+
+    @Override
+    public int getMaxCatalogNameLength() {
+        return METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8;
+    }
+
+    @Override
+    public ADBResultSet getSchemas() throws SQLException {
+        return getSchemas(metaStatement.connection.catalog, null);
+    }
+
+    @Override
+    public ADBResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
+        return metaStatement.executeGetSchemasQuery(catalog, schemaPattern);
+    }
+
+    @Override
+    public int getMaxSchemaNameLength() {
+        return METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8;
+    }
+
+    // Tables
+
+    @Override
+    public ADBResultSet getTableTypes() throws SQLException {
+        return metaStatement.executeGetTableTypesQuery();
+    }
+
+    @Override
+    public ADBResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types)
+            throws SQLException {
+        return metaStatement.executeGetTablesQuery(catalog, schemaPattern, tableNamePattern, types);
+    }
+
+    @Override
+    public ADBResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public int getMaxTableNameLength() {
+        return METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8;
+    }
+
+    @Override
+    public int getMaxColumnsInTable() {
+        return 0;
+    }
+
+    @Override
+    public int getMaxRowSize() {
+        return 0;
+    }
+
+    @Override
+    public boolean doesMaxRowSizeIncludeBlobs() {
+        return true;
+    }
+
+    // Columns
+
+    @Override
+    public ADBResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern,
+            String columnNamePattern) throws SQLException {
+        return metaStatement.executeGetColumnsQuery(catalog, schemaPattern, tableNamePattern, columnNamePattern);
+    }
+
+    @Override
+    public ADBResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern,
+            String columnNamePattern) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable)
+            throws SQLException {
+        // TODO:primary keys?
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public int getMaxColumnNameLength() {
+        return 0;
+    }
+
+    // Keys
+
+    @Override
+    public ADBResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
+        return metaStatement.executeGetPrimaryKeysQuery(catalog, schema, table);
+    }
+
+    @Override
+    public ADBResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable,
+            String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    // Indexes
+
+    @Override
+    public ADBResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public int getMaxColumnsInIndex() {
+        return 0;
+    }
+
+    @Override
+    public int getMaxIndexLength() {
+        return 0;
+    }
+
+    // Datatypes
+
+    @Override
+    public ADBResultSet getTypeInfo() throws SQLException {
+        return metaStatement.executeGetTypeInfoQuery();
+    }
+
+    @Override
+    public ADBResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern,
+            String attributeNamePattern) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public RowIdLifetime getRowIdLifetime() {
+        return RowIdLifetime.ROWID_UNSUPPORTED;
+    }
+
+    @Override
+    public long getMaxLogicalLobSize() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsRefCursors() {
+        return false;
+    }
+
+    // User-defined functions and procedures
+
+    @Override
+    public boolean supportsStoredProcedures() {
+        return false;
+    }
+
+    @Override
+    public ADBResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern,
+            String columnNamePattern) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern,
+            String columnNamePattern) throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public int getMaxProcedureNameLength() {
+        return METADATA_OBJECT_NAME_LENGTH_LIMIT_UTF8;
+    }
+
+    // Security
+
+    @Override
+    public ADBResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    @Override
+    public ADBResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern)
+            throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    // Other database objects
+
+    @Override
+    public ADBResultSet getClientInfoProperties() throws SQLException {
+        return metaStatement.executeEmptyResultQuery();
+    }
+
+    // SQL dialect: general
+
+    @Override
+    public boolean supportsMinimumSQLGrammar() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsCoreSQLGrammar() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsExtendedSQLGrammar() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsANSI92EntryLevelSQL() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsANSI92IntermediateSQL() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsANSI92FullSQL() {
+        return false;
+    }
+
+    @Override
+    public String getSQLKeywords() {
+        // keywords that are not also SQL:2003 keywords
+        return "adapter,apply,asc,autogenerated,btree,closed,compaction,compact,correlate,collection,dataset,"
+                + "dataverse,definition,desc,disconnect,div,explain,enforced,every,feed,flatten,fulltext,hints,if,"
+                + "index,ingestion,internal,keyword,key,known,letting,let,limit,load,missing,mod,nodegroup,ngram,"
+                + "offset,path,policy,pre-sorted,raw,refresh,returning,rtree,run,satisfies,secondary,some,stop,"
+                + "synonym,temporary,type,upsert,use,view,write";
+    }
+
+    @Override
+    public String getCatalogTerm() {
+        return "catalog";
+    }
+
+    @Override
+    public String getSchemaTerm() {
+        return "schema";
+    }
+
+    @Override
+    public String getProcedureTerm() {
+        return "procedure";
+    }
+
+    @Override
+    public int getMaxStatementLength() {
+        return 0;
+    }
+
+    // SQL dialect: identifiers
+
+    @Override
+    public String getExtraNameCharacters() {
+        return "";
+    }
+
+    @Override
+    public String getIdentifierQuoteString() {
+        return "`";
+    }
+
+    @Override
+    public boolean supportsMixedCaseIdentifiers() {
+        return true;
+    }
+
+    @Override
+    public boolean storesMixedCaseIdentifiers() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMixedCaseQuotedIdentifiers() {
+        return true;
+    }
+
+    @Override
+    public boolean storesMixedCaseQuotedIdentifiers() {
+        return false;
+    }
+
+    @Override
+    public boolean storesLowerCaseIdentifiers() {
+        return false;
+    }
+
+    @Override
+    public boolean storesLowerCaseQuotedIdentifiers() {
+        return false;
+    }
+
+    @Override
+    public boolean storesUpperCaseIdentifiers() {
+        return false;
+    }
+
+    @Override
+    public boolean storesUpperCaseQuotedIdentifiers() {
+        return false;
+    }
+
+    // SQL dialect: literals and parameters
+
+    @Override
+    public int getMaxBinaryLiteralLength() {
+        return 0;
+    }
+
+    @Override
+    public int getMaxCharLiteralLength() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsNamedParameters() {
+        // Procedures (CallableStatement) are not supported
+        return false;
+    }
+
+    // SQL dialect: functions and operators
+
+    @Override
+    public String getNumericFunctions() {
+        // NOTE: JDBC escape clause is not yet supported
+        // "add,div,mod,mult,neg,sub,abs,acos,asin,atan,atan2,ceil,cos,deg,degrees,e,exp,ln,log,floor,inf,nan,neginf,pi,posinf,power,rad,radians,random,round,sign,sin,sqrt,tan,trunc";
+        return "";
+    }
+
+    @Override
+    public String getStringFunctions() {
+        // NOTE: JDBC escape clause is not yet supported
+        // "contains,initcap,length,lower,ltrim,position,pos,regex_contains,regex_like,regex_position,regex_pos,regex_replace,repeat,replace,rtrim,split,substr,title,trim,upper";
+        return "";
+    }
+
+    @Override
+    public String getSystemFunctions() {
+        // NOTE: JDBC escape clause is not yet supported
+        return "";
+    }
+
+    @Override
+    public String getTimeDateFunctions() {
+        // TODO:review
+        return "current_date,current_time,current_datetime";
+    }
+
+    @Override
+    public boolean supportsConvert() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsConvert(int fromType, int toType) {
+        return false;
+    }
+
+    @Override
+    public String getSearchStringEscape() {
+        return "\\";
+    }
+
+    @Override
+    public boolean supportsLikeEscapeClause() {
+        return false;
+    }
+
+    @Override
+    public boolean nullPlusNonNullIsNull() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStoredFunctionsUsingCallSyntax() {
+        return false;
+    }
+
+    // SQL dialect: SELECT clause
+
+    @Override
+    public int getMaxColumnsInSelect() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsColumnAliasing() {
+        return true;
+    }
+
+    // SQL dialect: FROM clause
+
+    @Override
+    public boolean allTablesAreSelectable() {
+        return true;
+    }
+
+    @Override
+    public int getMaxTablesInSelect() {
+        return 0;
+    }
+
+    @Override
+    public boolean isCatalogAtStart() {
+        return true;
+    }
+
+    @Override
+    public String getCatalogSeparator() {
+        return ".";
+    }
+
+    @Override
+    public boolean supportsTableCorrelationNames() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsDifferentTableCorrelationNames() {
+        return true;
+    }
+
+    // SQL dialect: JOIN clause
+
+    @Override
+    public boolean supportsOuterJoins() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsLimitedOuterJoins() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFullOuterJoins() {
+        return false;
+    }
+
+    // SQL dialect: ORDER BY clause
+
+    @Override
+    public int getMaxColumnsInOrderBy() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsOrderByUnrelated() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsExpressionsInOrderBy() {
+        return true;
+    }
+
+    @Override
+    public boolean nullsAreSortedHigh() {
+        return false;
+    }
+
+    @Override
+    public boolean nullsAreSortedLow() {
+        return true;
+    }
+
+    @Override
+    public boolean nullsAreSortedAtStart() {
+        return false;
+    }
+
+    @Override
+    public boolean nullsAreSortedAtEnd() {
+        return false;
+    }
+
+    // SQL dialect: GROUP BY clause
+
+    @Override
+    public boolean supportsGroupBy() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGroupByUnrelated() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGroupByBeyondSelect() {
+        return true;
+    }
+
+    @Override
+    public int getMaxColumnsInGroupBy() {
+        return 0;
+    }
+
+    // SQL dialect: Subquery
+
+    @Override
+    public boolean supportsSubqueriesInComparisons() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSubqueriesInExists() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSubqueriesInIns() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSubqueriesInQuantifieds() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsCorrelatedSubqueries() {
+        return true;
+    }
+
+    // SQL dialect: Set operations
+
+    @Override
+    public boolean supportsUnion() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsUnionAll() {
+        return true;
+    }
+
+    // SQL dialect: DML statements
+
+    @Override
+    public boolean supportsCatalogsInDataManipulation() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSchemasInDataManipulation() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPositionedDelete() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsPositionedUpdate() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSelectForUpdate() {
+        return false;
+    }
+
+    // SQL dialect: DDL statements
+
+    // DDL: CREATE DATASET
+
+    @Override
+    public boolean supportsCatalogsInTableDefinitions() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSchemasInTableDefinitions() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsNonNullableColumns() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAlterTableWithAddColumn() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAlterTableWithDropColumn() {
+        return false;
+    }
+
+    // DDL: CREATE INDEX
+
+    @Override
+    public boolean supportsCatalogsInIndexDefinitions() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSchemasInIndexDefinitions() {
+        return true;
+    }
+
+    // DDL: GRANT / REVOKE (not supported)
+
+    @Override
+    public boolean supportsCatalogsInPrivilegeDefinitions() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSchemasInPrivilegeDefinitions() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsIntegrityEnhancementFacility() {
+        return false;
+    }
+
+    // SQL dialect: User-defined functions and procedures
+
+    @Override
+    public boolean allProceduresAreCallable() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsCatalogsInProcedureCalls() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSchemasInProcedureCalls() {
+        return false;
+    }
+
+    // Transactions
+
+    @Override
+    public boolean supportsTransactions() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMultipleTransactions() {
+        return true;
+    }
+
+    @Override
+    public int getDefaultTransactionIsolation() {
+        return Connection.TRANSACTION_READ_COMMITTED;
+    }
+
+    @Override
+    public boolean supportsTransactionIsolationLevel(int level) {
+        return Connection.TRANSACTION_READ_COMMITTED == level;
+    }
+
+    @Override
+    public boolean supportsDataDefinitionAndDataManipulationTransactions() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDataManipulationTransactionsOnly() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsOpenStatementsAcrossCommit() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsOpenCursorsAcrossCommit() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsOpenStatementsAcrossRollback() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsOpenCursorsAcrossRollback() {
+        return false;
+    }
+
+    @Override
+    public boolean dataDefinitionCausesTransactionCommit() {
+        return false;
+    }
+
+    @Override
+    public boolean dataDefinitionIgnoredInTransactions() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSavepoints() {
+        return false;
+    }
+
+    @Override
+    public boolean autoCommitFailureClosesAllResultSets() {
+        return false;
+    }
+
+    // Connection
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return metaStatement.connection;
+    }
+
+    @Override
+    public int getMaxConnections() {
+        return 0;
+    }
+
+    @Override
+    public String getURL() {
+        return metaStatement.connection.url;
+    }
+
+    @Override
+    public String getUserName() {
+        return metaStatement.connection.protocol.user;
+    }
+
+    @Override
+    public int getMaxUserNameLength() {
+        return 0;
+    }
+
+    // Statement
+
+    @Override
+    public int getMaxStatements() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsStatementPooling() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBatchUpdates() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsGetGeneratedKeys() {
+        return false;
+    }
+
+    @Override
+    public boolean generatedKeyAlwaysReturned() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMultipleResultSets() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMultipleOpenResults() {
+        return false;
+    }
+
+    @Override
+    public boolean locatorsUpdateCopy() {
+        return false;
+    }
+
+    // ResultSet
+
+    @Override
+    public boolean supportsResultSetType(int type) {
+        return type == ResultSet.TYPE_FORWARD_ONLY;
+    }
+
+    @Override
+    public boolean supportsResultSetConcurrency(int type, int concurrency) {
+        return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY;
+    }
+
+    @Override
+    public boolean supportsResultSetHoldability(int holdability) {
+        return holdability == ADBResultSet.RESULT_SET_HOLDABILITY;
+    }
+
+    @Override
+    public int getResultSetHoldability() {
+        return ADBResultSet.RESULT_SET_HOLDABILITY;
+    }
+
+    @Override
+    public boolean ownInsertsAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean othersInsertsAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean insertsAreDetected(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean ownUpdatesAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean othersUpdatesAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean updatesAreDetected(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean ownDeletesAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean othersDeletesAreVisible(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean deletesAreDetected(int type) {
+        return false;
+    }
+
+    @Override
+    public int getMaxCursorNameLength() {
+        return 0;
+    }
+
+    // Miscellaneous
+
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    @Override
+    public boolean usesLocalFiles() {
+        return false;
+    }
+
+    @Override
+    public boolean usesLocalFilePerTable() {
+        return false;
+    }
+
+    // Errors and warnings
+
+    @Override
+    public int getSQLStateType() {
+        return sqlStateSQL;
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return metaStatement.getErrorReporter();
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatatype.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatatype.java
new file mode 100644
index 0000000..e6634b5
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDatatype.java
@@ -0,0 +1,177 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.JDBCType;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+enum ADBDatatype {
+    TINYINT(1, "int8", JDBCType.TINYINT, Byte.class),
+    SMALLINT(2, "int16", JDBCType.SMALLINT, Short.class),
+    INTEGER(3, "int32", JDBCType.INTEGER, Integer.class),
+    BIGINT(4, "int64", JDBCType.BIGINT, Long.class),
+
+    //UINT8(5, null, JDBCType.OTHER),
+    //UINT16(6, null, JDBCType.OTHER),
+    //UINT32(7, null, JDBCType.OTHER),
+    //UINT64(8, null, JDBCType.OTHER),
+    //BINARY(9, "binary", JDBCType.VARBINARY, byte[].class),
+    //BITARRAY(10, null, JDBCType.VARBINARY),
+
+    FLOAT(11, "float", JDBCType.REAL, Float.class),
+    DOUBLE(12, "double", JDBCType.DOUBLE, Double.class),
+    STRING(13, "string", JDBCType.VARCHAR, String.class),
+    MISSING(14, "missing", JDBCType.OTHER, Void.class), // don't report as JDBCType.NULL
+    BOOLEAN(15, "boolean", JDBCType.BOOLEAN, Boolean.class),
+    DATETIME(16, "datetime", JDBCType.TIMESTAMP, java.sql.Timestamp.class),
+    DATE(17, "date", JDBCType.DATE, java.sql.Date.class),
+    TIME(18, "time", JDBCType.TIME, java.sql.Time.class),
+    DURATION(19, "duration", JDBCType.OTHER, String.class),
+
+    //POINT(20, "point", JDBCType.OTHER, Object.class),
+    //POINT3D(21, "point3d", JDBCType.OTHER, Object.class),
+
+    ARRAY(22, "array", JDBCType.OTHER, List.class),
+    MULTISET(23, "multiset", JDBCType.OTHER, List.class),
+    OBJECT(24, "object", JDBCType.OTHER, Map.class),
+
+    //SPARSOBJECT(25, null, null, JDBCType.OTHER),
+    //UNION(26, null, JDBCType.OTHER),
+    //ENUM(27, null, JDBCType.OTHER),
+    //TYPE(28, null, JDBCType.OTHER),
+
+    ANY(29, "any", JDBCType.OTHER, String.class),
+
+    //LINE(30, "line", JDBCType.OTHER, Object.class),
+    //POLYGON(31, "polygon", JDBCType.OTHER, Object.class),
+    //CIRCLE(32, "circle", JDBCType.OTHER, Object.class),
+    //RECTANGLE(33, "rectangle", JDBCType.OTHER, Object.class),
+    //INTERVAL(34, "interval", JDBCType.OTHER, Object.class),
+    //SYSTEM_NULL(35, null, null, JDBCType.OTHER),
+
+    YEARMONTHDURATION(36, "year-month-duration", JDBCType.OTHER, java.time.Period.class),
+    DAYTIMEDURATION(37, "day-time-duration", JDBCType.OTHER, java.time.Duration.class),
+    UUID(38, "uuid", JDBCType.OTHER, java.util.UUID.class),
+
+    //SHORTWITHOUTTYPEINFO(40, null, null, JDBCType.OTHER),
+
+    NULL(41, "null", JDBCType.NULL, Void.class);
+
+    //GEOMETRY(42, "geometry", JDBCType.OTHER, Object.class)
+
+    private static final ADBDatatype[] BY_TYPE_TAG;
+
+    private static final Map<String, ADBDatatype> BY_TYPE_NAME;
+
+    private final byte typeTag;
+
+    private final String typeName;
+
+    private final JDBCType jdbcType;
+
+    private final Class<?> javaClass;
+
+    ADBDatatype(int typeTag, String typeName, JDBCType jdbcType, Class<?> javaClass) {
+        this.typeTag = (byte) typeTag;
+        this.typeName = Objects.requireNonNull(typeName);
+        this.jdbcType = Objects.requireNonNull(jdbcType);
+        this.javaClass = Objects.requireNonNull(javaClass);
+    }
+
+    byte getTypeTag() {
+        return typeTag;
+    }
+
+    String getTypeName() {
+        return typeName;
+    }
+
+    JDBCType getJdbcType() {
+        return jdbcType;
+    }
+
+    Class<?> getJavaClass() {
+        return javaClass;
+    }
+
+    @Override
+    public String toString() {
+        return getTypeName();
+    }
+
+    boolean isDerived() {
+        return this == OBJECT || isList();
+    }
+
+    boolean isList() {
+        return this == ARRAY || this == MULTISET;
+    }
+
+    boolean isNullOrMissing() {
+        return this == NULL || this == MISSING;
+    }
+
+    static {
+        ADBDatatype[] allTypes = ADBDatatype.values();
+        ADBDatatype[] byTypeTag = new ADBDatatype[findMaxTypeTag(allTypes) + 1];
+        Map<String, ADBDatatype> byTypeName = new HashMap<>();
+        for (ADBDatatype t : allTypes) {
+            byTypeTag[t.typeTag] = t;
+            byTypeName.put(t.typeName, t);
+        }
+        BY_TYPE_TAG = byTypeTag;
+        BY_TYPE_NAME = byTypeName;
+    }
+
+    public static ADBDatatype findByTypeTag(byte typeTag) {
+        return typeTag >= 0 && typeTag < BY_TYPE_TAG.length ? BY_TYPE_TAG[typeTag] : null;
+    }
+
+    public static ADBDatatype findByTypeName(String typeName) {
+        return BY_TYPE_NAME.get(typeName);
+    }
+
+    private static int findMaxTypeTag(ADBDatatype[] allTypes) {
+        int maxTypeTag = 0;
+        for (ADBDatatype type : allTypes) {
+            if (type.typeTag < 0) {
+                throw new IllegalStateException(type.getTypeName());
+            }
+            maxTypeTag = Math.max(type.typeTag, maxTypeTag);
+        }
+        return maxTypeTag;
+    }
+
+    static String getDerivedRecordName(ADBDatatype type) {
+        switch (type) {
+            case OBJECT:
+                return "Record";
+            case ARRAY:
+                return "OrderedList";
+            case MULTISET:
+                return "UnorderedList";
+            default:
+                throw new IllegalArgumentException(String.valueOf(type));
+        }
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java
new file mode 100644
index 0000000..5d89612
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java
@@ -0,0 +1,255 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+
+public abstract class ADBDriverBase {
+
+    static final int JDBC_MAJOR_VERSION = 4;
+
+    static final int JDBC_MINOR_VERSION = 2;
+
+    static final String JDBC_SCHEME = "jdbc:";
+
+    static final String LOGGING_PROPERTY_SUFFIX = ".log.stderr";
+
+    protected final String urlScheme;
+
+    protected final int defaultApiPort;
+
+    protected volatile ADBDriverContext context;
+
+    public ADBDriverBase(String driverScheme, int defaultApiPort) {
+        this.urlScheme = JDBC_SCHEME + Objects.requireNonNull(driverScheme);
+        this.defaultApiPort = defaultApiPort;
+    }
+
+    protected static void registerDriver(java.sql.Driver driver) {
+        try {
+            DriverManager.registerDriver(driver);
+        } catch (SQLException e) {
+            DriverManager.println(String.format("Error registering driver %s. %s", driver.getClass().getName(), e));
+        }
+    }
+
+    private static void parseConnectionProperties(List<NameValuePair> inProps1, Properties inProps2,
+            ADBDriverContext driverContext, Map<ADBDriverProperty, Object> outProperties, SQLWarning outWarning)
+            throws SQLException {
+        for (NameValuePair pair : inProps1) {
+            String name = pair.getName();
+            String value = pair.getValue();
+            parseConnectionProperty(name, value, driverContext, outProperties, outWarning);
+        }
+        if (inProps2 != null) {
+            for (Enumeration<?> en = inProps2.propertyNames(); en.hasMoreElements();) {
+                String name = en.nextElement().toString();
+                String value = inProps2.getProperty(name);
+                parseConnectionProperty(name, value, driverContext, outProperties, outWarning);
+            }
+        }
+    }
+
+    private static void parseConnectionProperty(String name, String textValue, ADBDriverContext driverContext,
+            Map<ADBDriverProperty, Object> outProperties, SQLWarning outWarning) throws SQLException {
+        ADBDriverProperty property = driverContext.supportedProperties.get(name);
+        if (property == null) {
+            outWarning.setNextWarning(new SQLWarning(driverContext.errorReporter.warningParameterNotSupported(name)));
+            return;
+        }
+        if (textValue == null || textValue.isEmpty()) {
+            return;
+        }
+        Object value;
+        try {
+            value = Objects.requireNonNull(property.getValueParser().apply(textValue));
+        } catch (RuntimeException e) {
+            throw driverContext.errorReporter.errorParameterValueNotSupported(name);
+        }
+        outProperties.put(property, value);
+    }
+
+    private static Logger getParentLogger(Class<?> driverClass) {
+        return Logger.getLogger(driverClass.getPackage().getName());
+    }
+
+    protected static void setupLogging(Class<? extends java.sql.Driver> driverClass) {
+        String logLevel = System.getProperty(driverClass.getPackage().getName() + LOGGING_PROPERTY_SUFFIX);
+        if (logLevel == null) {
+            return;
+        }
+        Level level;
+        try {
+            level = Boolean.TRUE.toString().equals(logLevel) ? Level.ALL : Level.parse(logLevel.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            // ignore
+            return;
+        }
+
+        ConsoleHandler ch = new ConsoleHandler();
+        ch.setLevel(level);
+        Logger parentLogger = getParentLogger(driverClass);
+        parentLogger.setLevel(level);
+        parentLogger.addHandler(ch);
+    }
+
+    public boolean acceptsURL(String url) {
+        return url.startsWith(urlScheme);
+    }
+
+    public Connection connect(String url, Properties info) throws SQLException {
+        if (!acceptsURL(url)) {
+            return null;
+        }
+        URI subUri;
+        try {
+            subUri = new URI(url.substring(JDBC_SCHEME.length()));
+        } catch (URISyntaxException e) {
+            throw createErrorReporter().errorParameterValueNotSupported("URL");
+        }
+        String host = subUri.getHost();
+        if (host == null) {
+            throw createErrorReporter().errorParameterValueNotSupported("URL");
+        }
+        int port = subUri.getPort();
+        if (port <= 0) {
+            port = defaultApiPort;
+        }
+
+        String catalog = ADBProtocol.DEFAULT_DATAVERSE;
+        String schema = null;
+        String path = subUri.getPath();
+        if (path != null && path.length() > 1 && path.startsWith("/")) {
+            String[] dataverse = path.substring(1).split("/");
+            switch (dataverse.length) {
+                case 2:
+                    schema = dataverse[1];
+                    // fall thru to 1
+                case 1:
+                    catalog = dataverse[0];
+                    break;
+            }
+        }
+
+        List<NameValuePair> urlParams = URLEncodedUtils.parse(subUri, StandardCharsets.UTF_8);
+
+        ADBDriverContext driverContext = getOrCreateDriverContext();
+        Map<ADBDriverProperty, Object> properties = new HashMap<>();
+        SQLWarning warning = new SQLWarning();
+        parseConnectionProperties(urlParams, info, driverContext, properties, warning);
+        warning = warning.getNextWarning() != null ? warning.getNextWarning() : null;
+
+        ADBProtocol protocol = createProtocol(host, port, properties, driverContext);
+        try {
+            String databaseVersion = protocol.connect();
+            return createConnection(protocol, url, databaseVersion, catalog, schema, warning);
+        } catch (SQLException e) {
+            try {
+                protocol.close();
+            } catch (SQLException e2) {
+                e.addSuppressed(e2);
+            }
+            throw e;
+        }
+    }
+
+    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
+        Collection<ADBDriverProperty> supportedProperties = getOrCreateDriverContext().supportedProperties.values();
+        DriverPropertyInfo[] result = new DriverPropertyInfo[supportedProperties.size()];
+        int i = 0;
+        for (ADBDriverProperty property : supportedProperties) {
+            result[i++] = new DriverPropertyInfo(property.getPropertyName(),
+                    property.getDefaultValue() != null ? property.getDefaultValue().toString() : null);
+        }
+        return result;
+    }
+
+    public int getMajorVersion() {
+        return getOrCreateDriverContext().driverVersion.majorVersion;
+    }
+
+    public int getMinorVersion() {
+        return getOrCreateDriverContext().driverVersion.minorVersion;
+    }
+
+    public boolean jdbcCompliant() {
+        return false;
+    }
+
+    public Logger getParentLogger() {
+        return getParentLogger(getClass());
+    }
+
+    private ADBDriverContext getOrCreateDriverContext() {
+        ADBDriverContext ctx = context;
+        if (ctx == null) {
+            synchronized (this) {
+                ctx = context;
+                if (ctx == null) {
+                    context = ctx = createDriverContext();
+                }
+            }
+        }
+        return ctx;
+    }
+
+    protected ADBDriverContext createDriverContext() {
+        return new ADBDriverContext(getClass(), getDriverSupportedProperties(), createErrorReporter());
+    }
+
+    protected Collection<ADBDriverProperty> getDriverSupportedProperties() {
+        return Arrays.asList(ADBDriverProperty.Common.values());
+    }
+
+    protected ADBErrorReporter createErrorReporter() {
+        return new ADBErrorReporter();
+    }
+
+    protected ADBProtocol createProtocol(String host, int port, Map<ADBDriverProperty, Object> properties,
+            ADBDriverContext driverContext) throws SQLException {
+        return new ADBProtocol(host, port, properties, driverContext);
+    }
+
+    protected ADBConnection createConnection(ADBProtocol protocol, String url, String databaseVersion, String catalog,
+            String schema, SQLWarning connectWarning) {
+        return new ADBConnection(protocol, url, databaseVersion, catalog, schema, connectWarning);
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverContext.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverContext.java
new file mode 100644
index 0000000..89f01e9
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverContext.java
@@ -0,0 +1,86 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+final class ADBDriverContext {
+
+    final Class<? extends ADBDriverBase> driverClass;
+
+    final ADBErrorReporter errorReporter;
+
+    final ADBProductVersion driverVersion;
+
+    final Map<String, ADBDriverProperty> supportedProperties;
+
+    final Logger logger;
+
+    final ObjectReader genericObjectReader;
+
+    final ObjectWriter genericObjectWriter;
+
+    final ObjectReader admFormatObjectReader;
+
+    final ObjectWriter admFormatObjectWriter;
+
+    ADBDriverContext(Class<? extends ADBDriverBase> driverClass,
+            Collection<ADBDriverProperty> driverSupportedProperties, ADBErrorReporter errorReporter) {
+        this.driverClass = Objects.requireNonNull(driverClass);
+        this.errorReporter = Objects.requireNonNull(errorReporter);
+        this.logger = Logger.getLogger(driverClass.getName());
+        this.driverVersion = ADBProductVersion.parseDriverVersion(driverClass.getPackage());
+        this.supportedProperties = createPropertyIndexByName(driverSupportedProperties);
+
+        ObjectMapper genericObjectMapper = ADBProtocol.createObjectMapper();
+        this.genericObjectReader = genericObjectMapper.reader();
+        this.genericObjectWriter = genericObjectMapper.writer();
+        ObjectMapper admFormatObjectMapper = createADMFormatObjectMapper();
+        this.admFormatObjectReader = admFormatObjectMapper.reader();
+        this.admFormatObjectWriter = admFormatObjectMapper.writer();
+    }
+
+    protected ObjectMapper createADMFormatObjectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        SimpleModule serdeModule = new SimpleModule(driverClass.getName());
+        ADBStatement.configureSerialization(serdeModule);
+        ADBRowStore.configureDeserialization(mapper, serdeModule);
+        mapper.registerModule(serdeModule);
+        return mapper;
+    }
+
+    private Map<String, ADBDriverProperty> createPropertyIndexByName(Collection<ADBDriverProperty> properties) {
+        Map<String, ADBDriverProperty> m = new LinkedHashMap<>();
+        for (ADBDriverProperty p : properties) {
+            m.put(p.getPropertyName(), p);
+        }
+        return Collections.unmodifiableMap(m);
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverProperty.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverProperty.java
new file mode 100644
index 0000000..1d1e67e
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverProperty.java
@@ -0,0 +1,71 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+interface ADBDriverProperty {
+
+    String getPropertyName();
+
+    Function<String, ?> getValueParser();
+
+    Object getDefaultValue();
+
+    enum Common implements ADBDriverProperty {
+
+        USER("user", Function.identity(), null),
+        PASSWORD("password", Function.identity(), null),
+        CONNECT_TIMEOUT("connectTimeout", Integer::parseInt, null),
+        SOCKET_TIMEOUT("socketTimeout", Integer::parseInt, null),
+        MAX_WARNINGS("maxWarnings", Integer::parseInt, 10);
+
+        private final String propertyName;
+
+        private final Function<String, ?> valueParser;
+
+        private final Object defaultValue;
+
+        Common(String propertyName, Function<String, ?> valueParser, Object defaultValue) {
+            this.propertyName = Objects.requireNonNull(propertyName);
+            this.valueParser = Objects.requireNonNull(valueParser);
+            this.defaultValue = defaultValue;
+        }
+
+        @Override
+        public String getPropertyName() {
+            return propertyName;
+        }
+
+        public Function<String, ?> getValueParser() {
+            return valueParser;
+        }
+
+        public Object getDefaultValue() {
+            return defaultValue;
+        }
+
+        @Override
+        public String toString() {
+            return getPropertyName();
+        }
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java
new file mode 100644
index 0000000..f31e18a
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java
@@ -0,0 +1,207 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.IOException;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLInvalidAuthorizationSpecException;
+import java.sql.SQLNonTransientConnectionException;
+import java.sql.SQLTimeoutException;
+import java.sql.SQLTransientConnectionException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.http.conn.ConnectTimeoutException;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+final class ADBErrorReporter {
+
+    private static final List<Class<? extends IOException>> TRANSIENT_CONNECTION_ERRORS =
+            Arrays.asList(java.net.NoRouteToHostException.class, org.apache.http.NoHttpResponseException.class,
+                    org.apache.http.conn.HttpHostConnectException.class);
+
+    protected SQLException errorObjectClosed(Class<?> jdbcInterface) {
+        return new SQLException(String.format("%s is closed", jdbcInterface.getSimpleName()));
+    }
+
+    protected SQLFeatureNotSupportedException errorMethodNotSupported(Class<?> jdbcInterface, String methodName) {
+        return new SQLFeatureNotSupportedException(
+                String.format("Method %s.%s() is not supported", jdbcInterface.getName(), methodName));
+    }
+
+    protected SQLClientInfoException errorClientInfoMethodNotSupported(Class<?> jdbcInterface, String methodName) {
+        return new SQLClientInfoException(
+                String.format("Method %s.%s() is not supported", jdbcInterface.getName(), methodName),
+                Collections.emptyMap());
+    }
+
+    protected SQLException errorParameterNotSupported(String parameterName) {
+        return new SQLException(String.format("Unsupported parameter %s", parameterName));
+    }
+
+    protected String warningParameterNotSupported(String parameterName) {
+        return String.format("Unsupported parameter %s", parameterName);
+    }
+
+    protected SQLException errorParameterValueNotSupported(String parameterName) {
+        return new SQLException(String.format("Unsupported or invalid value of %s parameter", parameterName));
+    }
+
+    protected String warningParameterValueNotSupported(String parameterName) {
+        return String.format("Ignored unsupported or invalid value of %s parameter", parameterName);
+    }
+
+    protected SQLException errorIncompatibleMode(String mode) {
+        return new SQLException(String.format("Operation cannot be performed in %s mode", mode));
+    }
+
+    protected SQLException errorInProtocol() {
+        return new SQLNonTransientConnectionException("Protocol error", SQLState.CONNECTION_FAILURE.code);
+    }
+
+    protected SQLException errorInProtocol(String badValue) {
+        return new SQLNonTransientConnectionException(String.format("Protocol error. Unexpected %s", badValue),
+                SQLState.CONNECTION_FAILURE.code);
+    }
+
+    protected SQLException errorInProtocol(JsonProcessingException e) {
+        return new SQLNonTransientConnectionException(String.format("Protocol error. %s", getMessage(e)),
+                SQLState.CONNECTION_FAILURE.code, e);
+    }
+
+    protected SQLException errorInConnection(String badValue) {
+        return new SQLNonTransientConnectionException(String.format("Connection error. Unexpected %s", badValue),
+                SQLState.CONNECTION_FAILURE.code);
+    }
+
+    protected SQLException errorInConnection(IOException e) {
+        String message = String.format("Connection error. %s", getMessage(e));
+        return e instanceof ConnectTimeoutException ? errorTimeout(message, e)
+                : couldBeTransientConnectionError(e)
+                        ? new SQLTransientConnectionException(message, SQLState.CONNECTION_FAILURE.code, e)
+                        : new SQLNonTransientConnectionException(message, SQLState.CONNECTION_FAILURE.code, e);
+    }
+
+    protected SQLException errorClosingResource(IOException e) {
+        return new SQLException(String.format("Error closing resources. %s", getMessage(e)), e);
+    }
+
+    protected SQLInvalidAuthorizationSpecException errorAuth() {
+        return new SQLInvalidAuthorizationSpecException("Authentication/authorization error",
+                SQLState.CONNECTION_REJECTED.code);
+    }
+
+    protected SQLException errorColumnNotFound(String columnNameOrNumber) {
+        return new SQLException(String.format("Column %s was not found", columnNameOrNumber));
+    }
+
+    protected SQLException errorUnexpectedColumnValue(ADBDatatype type, String columnName) {
+        return new SQLException(
+                String.format("Unexpected value of type %s for column %s", type.getTypeName(), columnName));
+    }
+
+    protected SQLException errorUnwrapTypeMismatch(Class<?> iface) {
+        return new SQLException(String.format("Cannot unwrap to %s", iface.getName()));
+    }
+
+    protected SQLException errorInvalidStatementCategory() {
+        return new SQLException("Invalid statement category");
+    }
+
+    protected SQLException errorUnexpectedType(Class<?> type) {
+        return new SQLException(String.format("Unexpected type %s", type.getName()), SQLState.INVALID_DATE_TYPE.code);
+    }
+
+    protected SQLException errorUnexpectedType(byte typeTag) {
+        return new SQLException(String.format("Unexpected type %s", typeTag), SQLState.INVALID_DATE_TYPE.code);
+    }
+
+    protected SQLException errorUnexpectedType(ADBDatatype type) {
+        return new SQLException(String.format("Unexpected type %s", type.getTypeName()),
+                SQLState.INVALID_DATE_TYPE.code);
+    }
+
+    protected SQLException errorInvalidValueOfType(ADBDatatype type) {
+        return new SQLException(String.format("Invalid value of type %s", type), SQLState.INVALID_DATE_TYPE.code);
+    }
+
+    protected SQLException errorNoResult() {
+        return new SQLException("Result is unavailable");
+    }
+
+    protected SQLException errorBadResultSignature() {
+        return new SQLException("Cannot infer result columns");
+    }
+
+    protected SQLException errorNoCurrentRow() {
+        return new SQLException("No current row", SQLState.INVALID_CURSOR_POSITION.code);
+    }
+
+    protected SQLException errorInRequestGeneration(IOException e) {
+        return new SQLException(String.format("Cannot create request. %s", getMessage(e)), e);
+    }
+
+    protected SQLException errorInResultHandling(IOException e) {
+        return new SQLException(String.format("Cannot reading result. %s", getMessage(e)), e);
+    }
+
+    protected SQLTimeoutException errorTimeout() {
+        return new SQLTimeoutException();
+    }
+
+    protected SQLTimeoutException errorTimeout(String message, IOException cause) {
+        return new SQLTimeoutException(message, cause);
+    }
+
+    protected boolean couldBeTransientConnectionError(IOException e) {
+        if (e != null) {
+            for (Class<? extends IOException> c : TRANSIENT_CONNECTION_ERRORS) {
+                if (c.isInstance(e)) {
+                    return true;
+                }
+            }
+
+        }
+        return false;
+    }
+
+    protected String getMessage(Exception e) {
+        String message = e != null ? e.getMessage() : null;
+        return message != null ? message : "";
+    }
+
+    public enum SQLState {
+        CONNECTION_REJECTED("08004"),
+        CONNECTION_FAILURE("08006"),
+        INVALID_DATE_TYPE("HY004"),
+        INVALID_CURSOR_POSITION("HY108");
+
+        private final String code;
+
+        SQLState(String code) {
+            this.code = Objects.requireNonNull(code);
+        }
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBMetaStatement.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBMetaStatement.java
new file mode 100644
index 0000000..7fa8127
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBMetaStatement.java
@@ -0,0 +1,350 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.DatabaseMetaData;
+import java.sql.JDBCType;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class ADBMetaStatement extends ADBStatement {
+
+    public static final String SCHEMALESS = "SCHEMALESS";
+    public static final String TABLE = "TABLE";
+    public static final String VIEW = "VIEW";
+
+    protected ADBMetaStatement(ADBConnection connection) {
+        super(connection, null, null);
+    }
+
+    ADBResultSet executeGetCatalogsQuery() throws SQLException {
+        checkClosed();
+
+        StringBuilder sql = new StringBuilder(256);
+
+        sql.append("select TABLE_CAT ");
+        sql.append("from Metadata.`Dataverse` ");
+        sql.append("let name = decode_dataverse_name(DataverseName), ");
+        sql.append("TABLE_CAT = name[0] ");
+        sql.append("where array_length(name) between 1 and 2 ");
+        sql.append("group by TABLE_CAT ");
+        sql.append("order by TABLE_CAT");
+
+        return executeQueryImpl(sql.toString(), null);
+    }
+
+    ADBResultSet executeGetSchemasQuery(String catalog, String schemaPattern) throws SQLException {
+        checkClosed();
+
+        StringBuilder sql = new StringBuilder(512);
+        sql.append("select TABLE_SCHEM, TABLE_CATALOG ");
+        sql.append("from Metadata.`Dataverse` ");
+        sql.append("let name = decode_dataverse_name(DataverseName), ");
+        sql.append("TABLE_CATALOG = name[0], ");
+        sql.append("TABLE_SCHEM = case array_length(name) when 1 then null else name[1] end ");
+        sql.append("where array_length(name) between 1 and 2 ");
+        if (catalog != null) {
+            sql.append("and TABLE_CATALOG = $1 ");
+        }
+        if (schemaPattern != null) {
+            sql.append("and if_null(TABLE_SCHEM, '') like $2 ");
+        }
+        sql.append("order by TABLE_CATALOG, TABLE_SCHEM");
+
+        return executeQueryImpl(sql.toString(), Arrays.asList(catalog, schemaPattern));
+    }
+
+    ADBResultSet executeGetTablesQuery(String catalog, String schemaPattern, String tableNamePattern, String[] types)
+            throws SQLException {
+        checkClosed();
+
+        String datasetTermTabular = getDatasetTerm(true);
+        String datasetTermNonTabular = getDatasetTerm(false);
+        String viewTermTabular = getViewTerm(true);
+        String viewTermNonTabular = getViewTerm(false);
+
+        StringBuilder sql = new StringBuilder(1024);
+        sql.append("select TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TABLE_TYPE, null REMARKS, null TYPE_CAT, ");
+        sql.append("null TYPE_SCHEM, null TYPE_NAME, null SELF_REFERENCING_COL_NAME, null REF_GENERATION ");
+        sql.append("from Metadata.`Dataset` ds join Metadata.`Datatype` dt ");
+        sql.append("on ds.DatatypeDataverseName = dt.DataverseName and ds.DatatypeName = dt.DatatypeName ");
+        sql.append("let dvname = decode_dataverse_name(ds.DataverseName), ");
+        sql.append("isDataset = (ds.DatasetType = 'INTERNAL' or ds.DatasetType = 'EXTERNAL'), ");
+        sql.append("isView = ds.DatasetType = 'VIEW', ");
+        sql.append("hasFields = array_length(dt.Derived.Record.Fields) > 0, ");
+        sql.append("TABLE_CAT = dvname[0], ");
+        sql.append("TABLE_SCHEM = case array_length(dvname) when 1 then null else dvname[1] end, ");
+        sql.append("TABLE_NAME = ds.DatasetName, ");
+        sql.append("TABLE_TYPE = case ");
+        sql.append("when isDataset then (case when hasFields then '").append(datasetTermTabular).append("' else '")
+                .append(datasetTermNonTabular).append("' end) ");
+        sql.append("when isView then (case when hasFields then '").append(viewTermTabular).append("' else '")
+                .append(viewTermNonTabular).append("' end) ");
+        sql.append("else null end ");
+        sql.append("where array_length(dvname) between 1 and 2 ");
+        if (catalog != null) {
+            sql.append("and TABLE_CAT = $1 ");
+        }
+        if (schemaPattern != null) {
+            sql.append("and if_null(TABLE_SCHEM, '') like $2 ");
+        }
+        if (tableNamePattern != null) {
+            sql.append("and TABLE_NAME like $3 ");
+        }
+        sql.append("and TABLE_TYPE ").append(types != null ? "in $4" : "is not null").append(" ");
+        sql.append("order by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, TABLE_NAME");
+
+        return executeQueryImpl(sql.toString(),
+                Arrays.asList(catalog, schemaPattern, tableNamePattern, types != null ? Arrays.asList(types) : null));
+    }
+
+    ADBResultSet executeGetColumnsQuery(String catalog, String schemaPattern, String tableNamePattern,
+            String columnNamePattern) throws SQLException {
+        checkClosed();
+
+        StringBuilder sql = new StringBuilder(2048);
+        sql.append("select TABLE_CAT, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_SIZE, ");
+        sql.append("1 BUFFER_LENGTH, null DECIMAL_DIGITS, 2 NUM_PREC_RADIX, NULLABLE, ");
+        sql.append("null REMARKS, null COLUMN_DEF, DATA_TYPE SQL_DATA_TYPE,");
+        sql.append("0 SQL_DATETIME_SUB, COLUMN_SIZE CHAR_OCTET_LENGTH, ORDINAL_POSITION, ");
+        sql.append("case NULLABLE when 0 then 'NO' else 'YES' end IS_NULLABLE, ");
+        sql.append("null SCOPE_CATALOG, null SCOPE_SCHEMA, null SCOPE_TABLE, null SOURCE_DATA_TYPE, ");
+        sql.append("'NO' IS_AUTOINCREMENT, 'NO' IS_GENERATEDCOLUMN ");
+        sql.append("from Metadata.`Dataset` ds ");
+        sql.append("join Metadata.`Datatype` dt ");
+        sql.append("on ds.DatatypeDataverseName = dt.DataverseName and ds.DatatypeName = dt.DatatypeName ");
+        sql.append("unnest dt.Derived.Record.Fields as field at fieldpos ");
+        sql.append("left join Metadata.`Datatype` dt2 ");
+        sql.append(
+                "on field.FieldType = dt2.DatatypeName and ds.DataverseName = dt2.DataverseName and dt2.Derived is known ");
+        sql.append("let dvname = decode_dataverse_name(ds.DataverseName), ");
+        sql.append("TABLE_CAT = dvname[0], ");
+        sql.append("TABLE_SCHEM = case array_length(dvname) when 1 then null else dvname[1] end, ");
+        sql.append("TABLE_NAME = ds.DatasetName, ");
+        sql.append("COLUMN_NAME = field.FieldName, ");
+        sql.append("TYPE_NAME = case ");
+        for (ADBDatatype nestedType : new ADBDatatype[] { ADBDatatype.OBJECT, ADBDatatype.ARRAY,
+                ADBDatatype.MULTISET }) {
+            sql.append(String.format("when dt2.Derived.%s is known then '%s' ",
+                    ADBDatatype.getDerivedRecordName(nestedType), nestedType.getTypeName()));
+        }
+        sql.append("else field.FieldType end, ");
+        sql.append("DATA_TYPE = ");
+        sql.append("case TYPE_NAME ");
+        for (ADBDatatype type : ADBDatatype.values()) {
+            JDBCType jdbcType = type.getJdbcType();
+            if (type.isNullOrMissing() || jdbcType.equals(JDBCType.OTHER)) {
+                // will be handled by the 'else' clause
+                continue;
+            }
+            sql.append("when '").append(type.getTypeName()).append("' ");
+            sql.append("then ").append(jdbcType.getVendorTypeNumber()).append(" ");
+        }
+        sql.append("else ").append(JDBCType.OTHER.getVendorTypeNumber()).append(" end, ");
+
+        sql.append("COLUMN_SIZE = case field.FieldType when 'string' then 32767 else 8 end, "); // TODO:based on type
+        sql.append("ORDINAL_POSITION = fieldpos, ");
+        sql.append("NULLABLE = case when field.IsNullable or field.IsMissable then 1 else 0 end ");
+        sql.append("where array_length(dvname) between 1 and 2 ");
+
+        sql.append("and array_length(dt.Derived.Record.Fields) > 0 ");
+        if (catalog != null) {
+            sql.append("and TABLE_CAT = $1 ");
+        }
+        if (schemaPattern != null) {
+            sql.append("and if_null(TABLE_SCHEM, '') like $2 ");
+        }
+        if (tableNamePattern != null) {
+            sql.append("and TABLE_NAME like $3 ");
+        }
+        if (columnNamePattern != null) {
+            sql.append("and COLUMN_NAME like $4 ");
+        }
+        sql.append("order by TABLE_CAT, TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION");
+
+        return executeQueryImpl(sql.toString(),
+                Arrays.asList(catalog, schemaPattern, tableNamePattern, columnNamePattern));
+    }
+
+    ADBResultSet executeGetPrimaryKeysQuery(String catalog, String schema, String table) throws SQLException {
+        checkClosed();
+
+        StringBuilder sql = new StringBuilder(1024);
+        sql.append("select TABLE_CAT, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, KEY_SEQ, null PK_NAME ");
+        sql.append("from Metadata.`Dataset` ds unnest ds.InternalDetails.PrimaryKey pki at pkipos ");
+        sql.append("let dvname = decode_dataverse_name(ds.DataverseName), ");
+        sql.append("TABLE_CAT = dvname[0], ");
+        sql.append("TABLE_SCHEM = case array_length(dvname) when 1 then null else dvname[1] end, ");
+        sql.append("TABLE_NAME = ds.DatasetName, ");
+        sql.append("COLUMN_NAME = pki[0], ");
+        sql.append("KEY_SEQ = pkipos ");
+        sql.append("where array_length(dvname) between 1 and 2 ");
+        sql.append("and (every pk in ds.InternalDetails.PrimaryKey satisfies array_length(pk) = 1 end) ");
+        sql.append("and (every si in ds.InternalDetails.KeySourceIndicator satisfies si = 0 end ) ");
+        if (catalog != null) {
+            sql.append("and TABLE_CAT = $1 ");
+        }
+        if (schema != null) {
+            sql.append("and if_null(TABLE_SCHEM, '') like $2 ");
+        }
+        if (table != null) {
+            sql.append("and TABLE_NAME like $3 ");
+        }
+        sql.append("order by COLUMN_NAME");
+
+        return executeQueryImpl(sql.toString(), Arrays.asList(catalog, schema, table));
+    }
+
+    ADBResultSet executeGetTableTypesQuery() throws SQLException {
+        checkClosed();
+
+        LinkedHashSet<String> tableTypes = new LinkedHashSet<>();
+        tableTypes.add(getDatasetTerm(true));
+        tableTypes.add(getDatasetTerm(false));
+        tableTypes.add(getViewTerm(true));
+        tableTypes.add(getViewTerm(false));
+
+        List<ADBColumn> columns = Collections.singletonList(new ADBColumn("TABLE_TYPE", ADBDatatype.STRING, false));
+
+        AbstractValueSerializer stringSer = getADMFormatSerializer(String.class);
+        ArrayNode result = (ArrayNode) connection.protocol.driverContext.genericObjectReader.createArrayNode();
+        for (String tableType : tableTypes) {
+            result.addObject().put("TABLE_TYPE", stringSer.serializeToString(tableType));
+        }
+
+        return createSystemResultSet(columns, result);
+    }
+
+    ADBResultSet executeGetTypeInfoQuery() throws SQLException {
+        checkClosed();
+
+        AbstractValueSerializer int16Ser = getADMFormatSerializer(Short.class);
+        AbstractValueSerializer int32Ser = getADMFormatSerializer(Integer.class);
+        AbstractValueSerializer stringSer = getADMFormatSerializer(String.class);
+
+        List<ADBColumn> columns = new ArrayList<>();
+        columns.add(new ADBColumn("TYPE_NAME", ADBDatatype.STRING, false));
+        columns.add(new ADBColumn("DATA_TYPE", ADBDatatype.INTEGER, false));
+        columns.add(new ADBColumn("PRECISION", ADBDatatype.INTEGER, true));
+        columns.add(new ADBColumn("LITERAL_PREFIX", ADBDatatype.STRING, true));
+        columns.add(new ADBColumn("LITERAL_SUFFIX", ADBDatatype.STRING, true));
+        columns.add(new ADBColumn("CREATE_PARAMS", ADBDatatype.STRING, true));
+        columns.add(new ADBColumn("NULLABLE", ADBDatatype.SMALLINT, true));
+        columns.add(new ADBColumn("CASE_SENSITIVE", ADBDatatype.BOOLEAN, true));
+        columns.add(new ADBColumn("SEARCHABLE", ADBDatatype.SMALLINT, true));
+        columns.add(new ADBColumn("UNSIGNED_ATTRIBUTE", ADBDatatype.BOOLEAN, true));
+        columns.add(new ADBColumn("FIXED_PREC_SCALE", ADBDatatype.BOOLEAN, true));
+        columns.add(new ADBColumn("AUTO_INCREMENT", ADBDatatype.BOOLEAN, true));
+        columns.add(new ADBColumn("LOCAL_TYPE_NAME", ADBDatatype.STRING, true));
+        columns.add(new ADBColumn("MINIMUM_SCALE", ADBDatatype.SMALLINT, true));
+        columns.add(new ADBColumn("MAXIMUM_SCALE", ADBDatatype.SMALLINT, true));
+        columns.add(new ADBColumn("SQL_DATA_TYPE", ADBDatatype.INTEGER, true));
+        columns.add(new ADBColumn("SQL_DATETIME_SUB", ADBDatatype.INTEGER, true));
+        columns.add(new ADBColumn("NUM_PREC_RADIX", ADBDatatype.INTEGER, true));
+
+        ArrayNode result = (ArrayNode) connection.protocol.driverContext.genericObjectReader.createArrayNode();
+        populateTypeInfo(result.addObject(), ADBDatatype.BOOLEAN, 1, null, null, null, null, null, null, int16Ser,
+                int32Ser, stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.TINYINT, 3, 10, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.SMALLINT, 5, 10, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.INTEGER, 10, 10, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.BIGINT, 19, 10, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.FLOAT, 7, 2, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.DOUBLE, 15, 2, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.DATE, 32, null, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.TIME, 32, null, 0, 0, false, null, null, int16Ser, int32Ser,
+                stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.DATETIME, 32, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.YEARMONTHDURATION, 32, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.DAYTIMEDURATION, 32, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.DURATION, 32, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer); // TODO:precision
+        populateTypeInfo(result.addObject(), ADBDatatype.STRING, 32767, null, null, null, true, "'", "'", int16Ser,
+                int32Ser, stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.ARRAY, 32767, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer);
+        populateTypeInfo(result.addObject(), ADBDatatype.OBJECT, 32767, null, 0, 0, false, null, null, int16Ser,
+                int32Ser, stringSer);
+
+        return createSystemResultSet(columns, result);
+    }
+
+    private void populateTypeInfo(ObjectNode typeInfo, ADBDatatype type, int precision, Integer precisionRadix,
+            Integer minScale, Integer maxScale, Boolean searchable, String literalPrefix, String literalSuffix,
+            ADBPreparedStatement.AbstractValueSerializer int16Ser,
+            ADBPreparedStatement.AbstractValueSerializer int32Ser,
+            ADBPreparedStatement.AbstractValueSerializer stringSer) {
+        typeInfo.put("TYPE_NAME", stringSer.serializeToString(type.getTypeName()));
+        typeInfo.put("DATA_TYPE", int32Ser.serializeToString(type.getJdbcType().getVendorTypeNumber()));
+        typeInfo.put("PRECISION", int32Ser.serializeToString(precision));
+        typeInfo.put("LITERAL_PREFIX", literalPrefix != null ? stringSer.serializeToString(literalPrefix) : null);
+        typeInfo.put("LITERAL_SUFFIX", literalSuffix != null ? stringSer.serializeToString(literalSuffix) : null);
+        typeInfo.putNull("CREATE_PARAMS");
+        typeInfo.put("NULLABLE", int16Ser.serializeToString((short) DatabaseMetaData.typeNullable));
+        typeInfo.put("CASE_SENSITIVE", false);
+        typeInfo.put("SEARCHABLE",
+                int16Ser.serializeToString((short) (searchable == null ? DatabaseMetaData.typePredNone
+                        : searchable ? DatabaseMetaData.typeSearchable : DatabaseMetaData.typePredBasic)));
+        typeInfo.put("UNSIGNED_ATTRIBUTE", false);
+        typeInfo.put("FIXED_PREC_SCALE", false);
+        typeInfo.putNull("AUTO_INCREMENT");
+        typeInfo.putNull("LOCAL_TYPE_NAME");
+        typeInfo.put("MINIMUM_SCALE", minScale != null ? int16Ser.serializeToString(minScale.shortValue()) : null);
+        typeInfo.put("MAXIMUM_SCALE", maxScale != null ? int16Ser.serializeToString(maxScale.shortValue()) : null);
+        typeInfo.put("SQL_DATA_TYPE", int32Ser.serializeToString(type.getTypeTag()));
+        typeInfo.putNull("SQL_DATETIME_SUB");
+        typeInfo.put("NUM_PREC_RADIX", int32Ser.serializeToString(precisionRadix != null ? precisionRadix : 10));
+    }
+
+    ADBResultSet executeEmptyResultQuery() throws SQLException {
+        checkClosed();
+        return createEmptyResultSet();
+    }
+
+    @Override
+    ADBStatement getResultSetStatement(ADBResultSet rs) {
+        return null;
+    }
+
+    protected String getDatasetTerm(boolean tabular) {
+        return tabular ? TABLE : SCHEMALESS + " " + TABLE;
+    }
+
+    protected String getViewTerm(boolean tabular) {
+        return tabular ? VIEW : SCHEMALESS + " " + VIEW;
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBParameterMetaData.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBParameterMetaData.java
new file mode 100644
index 0000000..d612b3f
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBParameterMetaData.java
@@ -0,0 +1,86 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.ParameterMetaData;
+import java.sql.Types;
+import java.util.Objects;
+
+final class ADBParameterMetaData extends ADBWrapperSupport implements ParameterMetaData {
+
+    final ADBPreparedStatement statement;
+
+    final int parameterCount;
+
+    public ADBParameterMetaData(ADBPreparedStatement statement, int parameterCount) {
+        this.statement = Objects.requireNonNull(statement);
+        this.parameterCount = parameterCount;
+    }
+
+    @Override
+    public int getParameterCount() {
+        return parameterCount;
+    }
+
+    @Override
+    public int getParameterMode(int parameterIndex) {
+        return parameterModeIn;
+    }
+
+    @Override
+    public int getParameterType(int parameterIndex) {
+        return Types.OTHER; // any
+    }
+
+    @Override
+    public String getParameterTypeName(int parameterIndex) {
+        return "";
+    }
+
+    @Override
+    public String getParameterClassName(int parameterIndex) {
+        return Object.class.getName();
+    }
+
+    @Override
+    public int isNullable(int parameterIndex) {
+        return parameterNullable;
+    }
+
+    @Override
+    public boolean isSigned(int parameterIndex) {
+        return false;
+    }
+
+    @Override
+    public int getPrecision(int parameterIndex) {
+        return 0;
+    }
+
+    @Override
+    public int getScale(int parameterIndex) {
+        return 0;
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return statement.getErrorReporter();
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBPreparedStatement.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBPreparedStatement.java
new file mode 100644
index 0000000..98129fd
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBPreparedStatement.java
@@ -0,0 +1,496 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLType;
+import java.sql.SQLXML;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+
+final class ADBPreparedStatement extends ADBStatement implements PreparedStatement {
+
+    final String sql;
+
+    final List<Object> args;
+
+    final List<ADBColumn> resultColumns;
+
+    ADBPreparedStatement(ADBConnection connection, String sql, String catalog, String schema) throws SQLException {
+        super(connection, catalog, schema);
+        // TODO:timeout
+        ADBProtocol.QueryServiceResponse response =
+                connection.protocol.submitStatement(sql, null, false, true, 0, catalog, schema);
+        int parameterCount = connection.protocol.getStatementParameterCount(response);
+        boolean isQuery = connection.protocol.isStatementCategory(response,
+                ADBProtocol.QueryServiceResponse.StatementCategory.QUERY);
+        List<ADBColumn> columns = isQuery ? connection.protocol.getColumns(response) : Collections.emptyList();
+        this.sql = sql;
+        this.args = Arrays.asList(new Object[parameterCount]);
+        this.resultColumns = columns;
+    }
+
+    // Metadata
+
+    @Override
+    public ParameterMetaData getParameterMetaData() throws SQLException {
+        checkClosed();
+        return new ADBParameterMetaData(this, args.size());
+    }
+
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        checkClosed();
+        return new ADBResultSetMetaData(this, resultColumns);
+    }
+
+    // Execution
+
+    @Override
+    public ResultSet executeQuery() throws SQLException {
+        checkClosed();
+        return executeQueryImpl(sql, args);
+    }
+
+    @Override
+    public ADBResultSet executeQuery(String sql) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeQuery");
+    }
+
+    @Override
+    public long executeLargeUpdate() throws SQLException {
+        return executeUpdateImpl(sql, args);
+    }
+
+    @Override
+    public int executeUpdate() throws SQLException {
+        return executeUpdateImpl(sql, args);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeUpdate");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeUpdate");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeUpdate");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "executeUpdate");
+    }
+
+    @Override
+    public boolean execute() throws SQLException {
+        return executeImpl(sql, args);
+    }
+
+    @Override
+    public boolean execute(String sql) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "execute");
+    }
+
+    @Override
+    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "execute");
+    }
+
+    @Override
+    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "execute");
+    }
+
+    @Override
+    public boolean execute(String sql, String[] columnNames) throws SQLException {
+        // Prohibited on PreparedStatement
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "execute");
+    }
+
+    @Override
+    public void addBatch() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "addBatch");
+    }
+
+    @Override
+    public void setEscapeProcessing(boolean enable) throws SQLException {
+        checkClosed();
+        // ignore as the statement has already been parsed
+    }
+
+    // Parameter bindings
+
+    @Override
+    public void clearParameters() throws SQLException {
+        checkClosed();
+        for (int i = 0, n = args.size(); i < n; i++) {
+            args.set(i, null);
+        }
+    }
+
+    private int argIndex(int parameterIndex) throws SQLException {
+        boolean ok = 0 < parameterIndex && parameterIndex <= args.size();
+        if (!ok) {
+            throw getErrorReporter().errorParameterValueNotSupported("parameterIndex");
+        }
+        return parameterIndex - 1;
+    }
+
+    private void setArg(int parameterIndex, Object v) throws SQLException {
+        args.set(argIndex(parameterIndex), v);
+    }
+
+    // Basic types
+
+    @Override
+    public void setNull(int parameterIndex, int sqlType) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, null);
+    }
+
+    @Override
+    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
+        checkClosed();
+        setNull(parameterIndex, sqlType);
+    }
+
+    @Override
+    public void setBoolean(int parameterIndex, boolean v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setByte(int parameterIndex, byte v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setShort(int parameterIndex, short v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setInt(int parameterIndex, int v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setLong(int parameterIndex, long v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setFloat(int parameterIndex, float v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setDouble(int parameterIndex, double v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setBigDecimal(int parameterIndex, BigDecimal v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setString(int parameterIndex, String v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setNString(int parameterIndex, String v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setDate(int parameterIndex, java.sql.Date v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setDate(int parameterIndex, java.sql.Date v, Calendar cal) throws SQLException {
+        checkClosed();
+        setDate(parameterIndex, v);
+    }
+
+    @Override
+    public void setTime(int parameterIndex, java.sql.Time v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setTime(int parameterIndex, java.sql.Time v, Calendar cal) throws SQLException {
+        checkClosed();
+        setTime(parameterIndex, v);
+    }
+
+    @Override
+    public void setTimestamp(int parameterIndex, java.sql.Timestamp v) throws SQLException {
+        checkClosed();
+        setArg(parameterIndex, v);
+    }
+
+    @Override
+    public void setTimestamp(int parameterIndex, java.sql.Timestamp v, Calendar cal) throws SQLException {
+        checkClosed();
+        setTimestamp(parameterIndex, v);
+    }
+
+    // Generic (setObject)
+
+    @Override
+    public void setObject(int parameterIndex, Object v) throws SQLException {
+        checkClosed();
+        if (v == null || isSetObjectCompatible(v.getClass())) {
+            setArg(parameterIndex, v);
+        } else {
+            throw getErrorReporter().errorParameterValueNotSupported("object");
+        }
+    }
+
+    @Override
+    public void setObject(int parameterIndex, Object v, int targetSqlType) throws SQLException {
+        setObject(parameterIndex, v); // TODO:revisit
+    }
+
+    @Override
+    public void setObject(int parameterIndex, Object v, SQLType targetSqlType) throws SQLException {
+        setObject(parameterIndex, v, targetSqlType.getVendorTypeNumber()); // TODO:revisit
+    }
+
+    @Override
+    public void setObject(int parameterIndex, Object v, int targetSqlType, int scaleOrLength) throws SQLException {
+        setObject(parameterIndex, v, targetSqlType); // TODO:revisit
+    }
+
+    @Override
+    public void setObject(int parameterIndex, Object v, SQLType targetSqlType, int scaleOrLength) throws SQLException {
+        setObject(parameterIndex, v, targetSqlType.getVendorTypeNumber()); // TODO:revisit
+    }
+
+    // Unsupported
+
+    @Override
+    public void setBytes(int parameterIndex, byte[] v) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBytes");
+    }
+
+    @Override
+    public void setRef(int parameterIndex, Ref x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setRef");
+    }
+
+    @Override
+    public void setRowId(int parameterIndex, RowId x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setRowId");
+    }
+
+    @Override
+    public void setURL(int parameterIndex, URL v) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setURL");
+    }
+
+    // Unsupported - streams
+
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream v) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setAsciiStream");
+    }
+
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setAsciiStream");
+    }
+
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream v, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setAsciiStream");
+    }
+
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBinaryStream");
+    }
+
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBinaryStream");
+    }
+
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBinaryStream");
+    }
+
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setCharacterStream");
+    }
+
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setCharacterStream");
+    }
+
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setCharacterStream");
+    }
+
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setNCharacterStream");
+    }
+
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setNCharacterStream");
+    }
+
+    @Override
+    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setUnicodeStream");
+    }
+
+    // Unsupported - LOB, Array, SQLXML
+
+    @Override
+    public void setArray(int parameterIndex, Array x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setArray");
+    }
+
+    @Override
+    public void setBlob(int parameterIndex, Blob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBlob");
+    }
+
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBlob");
+    }
+
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setBlob");
+    }
+
+    @Override
+    public void setClob(int parameterIndex, Clob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setClob");
+    }
+
+    @Override
+    public void setClob(int parameterIndex, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setClob");
+    }
+
+    @Override
+    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setClob");
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, NClob value) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setNClob");
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setNClob");
+    }
+
+    @Override
+    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setNClob");
+    }
+
+    @Override
+    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(PreparedStatement.class, "setSQLXML");
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProductVersion.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProductVersion.java
new file mode 100644
index 0000000..5255d75
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProductVersion.java
@@ -0,0 +1,98 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ADBProductVersion {
+
+    public static final String ASTERIXDB = "Apache AsterixDB";
+
+    private static final Pattern DATABASE_VERSION_PATTERN =
+            Pattern.compile("(?<name>[^/]+)(?:/(?<ver>(?:(?<vermj>\\d+)(?:\\.(?<vermn>\\d+))?)?.*))?");
+
+    final String productName;
+
+    final String productVersion;
+
+    final int majorVersion;
+
+    final int minorVersion;
+
+    private ADBProductVersion(String productName, String productVersion, int majorVersion, int minorVersion) {
+        this.productName = productName != null ? productName : ASTERIXDB;
+        this.productVersion = productVersion != null ? productVersion : majorVersion + "." + minorVersion;
+        this.majorVersion = majorVersion;
+        this.minorVersion = minorVersion;
+    }
+
+    static ADBProductVersion parseDriverVersion(Package driverPackage) {
+        int majorVersion = 0, minorVersion = 0;
+        String productName = driverPackage.getImplementationTitle();
+        if (productName == null) {
+            productName = ASTERIXDB;
+        }
+        String productVersion = driverPackage.getImplementationVersion();
+        if (productVersion != null) {
+            String[] v = productVersion.split("\\.");
+            try {
+                majorVersion = Integer.parseInt(v[0]);
+                if (v.length > 1) {
+                    minorVersion = Integer.parseInt(v[1]);
+                }
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+        return new ADBProductVersion(productName, productVersion, majorVersion, minorVersion);
+    }
+
+    static ADBProductVersion parseDatabaseVersion(String serverVersion) {
+        String dbProductName = null;
+        String dbProductVersion = null;
+        int dbMajorVersion = 0;
+        int dbMinorVersion = 0;
+        if (serverVersion != null) {
+            Matcher m = DATABASE_VERSION_PATTERN.matcher(serverVersion);
+            if (m.matches()) {
+                dbProductName = m.group("name");
+                dbProductVersion = m.group("ver");
+                String vermj = m.group("vermj");
+                String vermn = m.group("vermn");
+                if (vermj != null) {
+                    try {
+                        dbMajorVersion = Integer.parseInt(vermj);
+                    } catch (NumberFormatException e) {
+                        // ignore (overflow)
+                    }
+                }
+                if (vermn != null) {
+                    try {
+                        dbMinorVersion = Integer.parseInt(vermn);
+                    } catch (NumberFormatException e) {
+                        // ignore (overflow)
+                    }
+                }
+            }
+        }
+        return new ADBProductVersion(dbProductName, dbProductVersion, dbMajorVersion, dbMinorVersion);
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java
new file mode 100644
index 0000000..e8a36f5
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java
@@ -0,0 +1,641 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.entity.ContentProducer;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.EntityTemplate;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicNameValuePair;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+public class ADBProtocol {
+
+    private static final String QUERY_ENDPOINT_PATH = "/query/service";
+    private static final String QUERY_RESULT_ENDPOINT_PATH = "/query/service/result";
+
+    private static final String STATEMENT = "statement";
+    private static final String ARGS = "args";
+    private static final String MODE = "mode";
+    private static final String READ_ONLY = "readonly";
+    private static final String DATAVERSE = "dataverse";
+    private static final String TIMEOUT = "timeout";
+    private static final String SIGNATURE = "signature";
+    private static final String COMPILE_ONLY = "compile-only";
+    private static final String CLIENT_TYPE = "client-type";
+    private static final String PLAN_FORMAT = "plan-format";
+    private static final String MAX_WARNINGS = "max-warnings";
+
+    private static final String MODE_DEFERRED = "deferred";
+    private static final String CLIENT_TYPE_JDBC = "jdbc";
+    private static final String RESULTS = "results";
+    private static final String FORMAT_LOSSLESS_ADM = "lossless-adm";
+    private static final String PLAN_FORMAT_STRING = "string";
+
+    private static final String OPTIONAL_TYPE_SUFFIX = "?";
+    static final char TEXT_DELIMITER = ':';
+    static final String EXPLAIN_ONLY_RESULT_COLUMN_NAME = "$1";
+    static final String DEFAULT_DATAVERSE = "Default";
+
+    final ADBDriverContext driverContext;
+    final HttpClientConnectionManager httpConnectionManager;
+    final HttpClientContext httpClientContext;
+    final CloseableHttpClient httpClient;
+    final URI queryEndpoint;
+    final URI queryResultEndpoint;
+    final String user;
+    final int maxWarnings;
+
+    protected ADBProtocol(String host, int port, Map<ADBDriverProperty, Object> params, ADBDriverContext driverContext)
+            throws SQLException {
+        URI queryEndpoint = createEndpointUri(host, port, QUERY_ENDPOINT_PATH, driverContext.errorReporter);
+        URI queryResultEndpoint =
+                createEndpointUri(host, port, QUERY_RESULT_ENDPOINT_PATH, driverContext.errorReporter);
+        PoolingHttpClientConnectionManager httpConnectionManager = new PoolingHttpClientConnectionManager();
+        int maxConnections = Math.max(16, Runtime.getRuntime().availableProcessors());
+        httpConnectionManager.setDefaultMaxPerRoute(maxConnections);
+        httpConnectionManager.setMaxTotal(maxConnections);
+        SocketConfig.Builder socketConfigBuilder = null;
+        Number socketTimeoutMillis = (Number) params.get(ADBDriverProperty.Common.SOCKET_TIMEOUT);
+        if (socketTimeoutMillis != null) {
+            socketConfigBuilder = SocketConfig.custom();
+            socketConfigBuilder.setSoTimeout(socketTimeoutMillis.intValue());
+        }
+        if (socketConfigBuilder != null) {
+            httpConnectionManager.setDefaultSocketConfig(socketConfigBuilder.build());
+        }
+        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
+        Number connectTimeoutMillis = (Number) params.get(ADBDriverProperty.Common.CONNECT_TIMEOUT);
+        if (connectTimeoutMillis != null) {
+            requestConfigBuilder.setConnectionRequestTimeout(connectTimeoutMillis.intValue());
+            requestConfigBuilder.setConnectTimeout(connectTimeoutMillis.intValue());
+        }
+        if (socketTimeoutMillis != null) {
+            requestConfigBuilder.setSocketTimeout(socketTimeoutMillis.intValue());
+        }
+        RequestConfig requestConfig = requestConfigBuilder.build();
+
+        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+        httpClientBuilder.setConnectionManager(httpConnectionManager);
+        httpClientBuilder.setConnectionManagerShared(true);
+        httpClientBuilder.setDefaultRequestConfig(requestConfig);
+        String user = (String) params.get(ADBDriverProperty.Common.USER);
+        if (user != null) {
+            String password = (String) params.get(ADBDriverProperty.Common.PASSWORD);
+            httpClientBuilder.setDefaultCredentialsProvider(createCredentialsProvider(user, password));
+        }
+
+        Number maxWarnings = ((Number) params.getOrDefault(ADBDriverProperty.Common.MAX_WARNINGS,
+                ADBDriverProperty.Common.MAX_WARNINGS.getDefaultValue()));
+
+        this.user = user;
+        this.queryEndpoint = queryEndpoint;
+        this.queryResultEndpoint = queryResultEndpoint;
+        this.httpConnectionManager = httpConnectionManager;
+        this.httpClient = httpClientBuilder.build();
+        this.httpClientContext = createHttpClientContext(queryEndpoint);
+        this.driverContext = Objects.requireNonNull(driverContext);
+        this.maxWarnings = Math.max(maxWarnings.intValue(), 0);
+    }
+
+    private static URI createEndpointUri(String host, int port, String path, ADBErrorReporter errorReporter)
+            throws SQLException {
+        try {
+            return new URI("http", null, host, port, path, null, null);
+        } catch (URISyntaxException e) {
+            throw errorReporter.errorParameterValueNotSupported("endpoint " + host + ":" + port);
+        }
+    }
+
+    private static CredentialsProvider createCredentialsProvider(String user, String password) {
+        CredentialsProvider cp = new BasicCredentialsProvider();
+        cp.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user, password));
+        return cp;
+    }
+
+    void close() throws SQLException {
+        try {
+            httpClient.close();
+        } catch (IOException e) {
+            throw getErrorReporter().errorClosingResource(e);
+        } finally {
+            httpConnectionManager.shutdown();
+        }
+    }
+
+    String connect() throws SQLException {
+        String databaseVersion = pingImpl(-1, true); // TODO:review timeout
+        if (getLogger().isLoggable(Level.FINE)) {
+            getLogger().log(Level.FINE, String.format("connected to '%s' at %s", databaseVersion, queryEndpoint));
+        }
+        return databaseVersion;
+    }
+
+    boolean ping(int timeoutSeconds) {
+        try {
+            pingImpl(timeoutSeconds, false);
+            return true;
+        } catch (SQLException e) {
+            return false;
+        }
+    }
+
+    private String pingImpl(int timeoutSeconds, boolean fetchDatabaseVersion) throws SQLException {
+        //TODO: support timeoutSeconds: -1 = use default, 0 = indefinite ?
+        HttpOptions httpOptions = new HttpOptions(queryEndpoint);
+        try (CloseableHttpResponse response = httpClient.execute(httpOptions, httpClientContext)) {
+            int statusCode = response.getStatusLine().getStatusCode();
+            switch (statusCode) {
+                case HttpStatus.SC_OK:
+                    String databaseVersion = null;
+                    if (fetchDatabaseVersion) {
+                        Header serverHeader = response.getFirstHeader(HttpHeaders.SERVER);
+                        if (serverHeader != null) {
+                            databaseVersion = serverHeader.getValue();
+                        }
+                    }
+                    return databaseVersion;
+                case HttpStatus.SC_UNAUTHORIZED:
+                case HttpStatus.SC_FORBIDDEN:
+                    throw getErrorReporter().errorAuth();
+                default:
+                    throw getErrorReporter().errorInConnection(String.valueOf(response.getStatusLine()));
+            }
+        } catch (IOException e) {
+            throw getErrorReporter().errorInConnection(e);
+        }
+    }
+
+    QueryServiceResponse submitStatement(String sql, List<?> args, boolean forceReadOnly, boolean compileOnly,
+            int timeoutSeconds, String catalog, String schema) throws SQLException {
+        HttpPost httpPost = new HttpPost(queryEndpoint);
+        httpPost.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON
+                .withParameters(new BasicNameValuePair(FORMAT_LOSSLESS_ADM, Boolean.TRUE.toString())).toString());
+
+        ByteArrayOutputStreamImpl baos = new ByteArrayOutputStreamImpl(512);
+        try {
+            JsonGenerator jsonGen = driverContext.genericObjectWriter.createGenerator(baos, JsonEncoding.UTF8);
+            jsonGen.writeStartObject();
+            jsonGen.writeStringField(CLIENT_TYPE, CLIENT_TYPE_JDBC);
+            jsonGen.writeStringField(MODE, MODE_DEFERRED);
+            jsonGen.writeStringField(STATEMENT, sql);
+            jsonGen.writeBooleanField(SIGNATURE, true);
+            jsonGen.writeStringField(PLAN_FORMAT, PLAN_FORMAT_STRING);
+            jsonGen.writeNumberField(MAX_WARNINGS, maxWarnings);
+            if (compileOnly) {
+                jsonGen.writeBooleanField(COMPILE_ONLY, true);
+            }
+            if (forceReadOnly) {
+                jsonGen.writeBooleanField(READ_ONLY, true);
+            }
+            if (timeoutSeconds > 0) {
+                jsonGen.writeStringField(TIMEOUT, timeoutSeconds + "s");
+            }
+            if (catalog != null) {
+                jsonGen.writeStringField(DATAVERSE, schema != null ? catalog + "/" + schema : catalog);
+            }
+            if (args != null && !args.isEmpty()) {
+                jsonGen.writeFieldName(ARGS);
+                driverContext.admFormatObjectWriter.writeValue(jsonGen, args);
+            }
+            jsonGen.writeEndObject();
+            jsonGen.flush();
+        } catch (InvalidDefinitionException e) {
+            throw getErrorReporter().errorUnexpectedType(e.getType().getRawClass());
+        } catch (IOException e) {
+            throw getErrorReporter().errorInRequestGeneration(e);
+        }
+
+        System.err.printf("<ADB_DRIVER_SQL>%n%s%n</ADB_DRIVER_SQL>%n", sql);
+
+        if (getLogger().isLoggable(Level.FINE)) {
+            getLogger().log(Level.FINE, String.format("%s { %s } with args { %s }", compileOnly ? "compile" : "execute",
+                    sql, args != null ? args : ""));
+        }
+
+        httpPost.setEntity(new EntityTemplateImpl(baos, ContentType.APPLICATION_JSON));
+        try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost, httpClientContext)) {
+            return handlePostQueryResponse(httpResponse);
+        } catch (JsonProcessingException e) {
+            throw getErrorReporter().errorInProtocol(e);
+        } catch (IOException e) {
+            throw getErrorReporter().errorInConnection(e);
+        }
+    }
+
+    private QueryServiceResponse handlePostQueryResponse(CloseableHttpResponse httpResponse)
+            throws SQLException, IOException {
+        int httpStatus = httpResponse.getStatusLine().getStatusCode();
+        switch (httpStatus) {
+            case HttpStatus.SC_OK:
+            case HttpStatus.SC_BAD_REQUEST:
+            case HttpStatus.SC_INTERNAL_SERVER_ERROR:
+            case HttpStatus.SC_SERVICE_UNAVAILABLE:
+                break;
+            case HttpStatus.SC_UNAUTHORIZED:
+            case HttpStatus.SC_FORBIDDEN:
+                throw getErrorReporter().errorAuth();
+            default:
+                throw getErrorReporter().errorInProtocol(httpResponse.getStatusLine().toString());
+        }
+        QueryServiceResponse response;
+        try (InputStream contentStream = httpResponse.getEntity().getContent()) {
+            response = driverContext.genericObjectReader.readValue(contentStream, QueryServiceResponse.class);
+        }
+        QueryServiceResponse.Status status = response.status;
+        if (httpStatus == HttpStatus.SC_OK && status == QueryServiceResponse.Status.SUCCESS) {
+            return response;
+        }
+        if (status == QueryServiceResponse.Status.TIMEOUT) {
+            throw getErrorReporter().errorTimeout();
+        }
+        SQLException exc = getErrorIfExists(response);
+        if (exc != null) {
+            throw exc;
+        } else {
+            throw getErrorReporter().errorInProtocol(httpResponse.getStatusLine().toString());
+        }
+    }
+
+    JsonParser fetchResult(QueryServiceResponse response) throws SQLException {
+        if (response.handle == null) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        int p = response.handle.lastIndexOf("/");
+        if (p < 0) {
+            throw getErrorReporter().errorInProtocol(response.handle);
+        }
+        String handlePath = response.handle.substring(p);
+        URI resultRequestURI;
+        try {
+            resultRequestURI = new URI(queryResultEndpoint + handlePath);
+        } catch (URISyntaxException e) {
+            throw getErrorReporter().errorInProtocol(handlePath);
+        }
+        HttpGet httpGet = new HttpGet(resultRequestURI);
+        httpGet.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
+
+        CloseableHttpResponse httpResponse = null;
+        InputStream httpContentStream = null;
+        JsonParser parser = null;
+        try {
+            httpResponse = httpClient.execute(httpGet, httpClientContext);
+            int httpStatus = httpResponse.getStatusLine().getStatusCode();
+            if (httpStatus != HttpStatus.SC_OK) {
+                throw getErrorReporter().errorNoResult();
+            }
+            HttpEntity entity = httpResponse.getEntity();
+            httpContentStream = entity.getContent();
+            parser = driverContext.genericObjectReader
+                    .createParser(new InputStreamWithAttachedResource(httpContentStream, httpResponse));
+            if (!advanceToArrayField(parser, RESULTS)) {
+                throw getErrorReporter().errorInProtocol();
+            }
+            parser.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
+            return parser;
+        } catch (SQLException e) {
+            closeQuietly(e, parser, httpContentStream, httpResponse);
+            throw e;
+        } catch (JsonProcessingException e) {
+            closeQuietly(e, parser, httpContentStream, httpResponse);
+            throw getErrorReporter().errorInProtocol(e);
+        } catch (IOException e) {
+            closeQuietly(e, parser, httpContentStream, httpResponse);
+            throw getErrorReporter().errorInConnection(e);
+        }
+    }
+
+    private boolean advanceToArrayField(JsonParser parser, String fieldName) throws IOException {
+        if (parser.nextToken() != JsonToken.START_OBJECT) {
+            return false;
+        }
+        for (;;) {
+            JsonToken token = parser.nextValue();
+            if (token == null || token == JsonToken.END_OBJECT) {
+                return false;
+            }
+            if (parser.currentName().equals(fieldName)) {
+                return token == JsonToken.START_ARRAY;
+            } else if (token.isStructStart()) {
+                parser.skipChildren();
+            } else {
+                parser.nextToken();
+            }
+        }
+    }
+
+    ArrayNode fetchExplainOnlyResult(QueryServiceResponse response,
+            ADBPreparedStatement.AbstractValueSerializer stringSer) throws SQLException {
+        if (response.results == null || response.results.isEmpty()) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        Object v = response.results.get(0);
+        if (!(v instanceof String)) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        try (BufferedReader br = new BufferedReader(new StringReader(v.toString()))) {
+            ArrayNode arrayNode = (ArrayNode) driverContext.genericObjectReader.createArrayNode();
+            String line;
+            while ((line = br.readLine()) != null) {
+                arrayNode.addObject().put(ADBProtocol.EXPLAIN_ONLY_RESULT_COLUMN_NAME,
+                        stringSer.serializeToString(line));
+            }
+            return arrayNode;
+        } catch (IOException e) {
+            throw getErrorReporter().errorInResultHandling(e);
+        }
+    }
+
+    private HttpClientContext createHttpClientContext(URI uri) {
+        HttpClientContext hcCtx = HttpClientContext.create();
+        AuthCache ac = new BasicAuthCache();
+        ac.put(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), new BasicScheme());
+        hcCtx.setAuthCache(ac);
+        return hcCtx;
+    }
+
+    boolean isStatementCategory(QueryServiceResponse response, QueryServiceResponse.StatementCategory category) {
+        return response.plans != null && category.equals(response.plans.statementCategory);
+    }
+
+    int getUpdateCount(QueryServiceResponse response) {
+        // TODO:need to get update count through the response
+        return isStatementCategory(response, QueryServiceResponse.StatementCategory.UPDATE) ? 1 : 0;
+    }
+
+    SQLException getErrorIfExists(QueryServiceResponse response) {
+        if (response.errors != null && !response.errors.isEmpty()) {
+            QueryServiceResponse.Message err = response.errors.get(0);
+            return new SQLException(err.msg, null, err.code);
+        }
+        return null;
+    }
+
+    List<QueryServiceResponse.Message> getWarningIfExists(QueryServiceResponse response) {
+        return response.warnings != null && !response.warnings.isEmpty() ? response.warnings : null;
+    }
+
+    SQLWarning createSQLWarning(List<QueryServiceResponse.Message> warnings) {
+        SQLWarning sqlWarning = null;
+        ListIterator<QueryServiceResponse.Message> i = warnings.listIterator(warnings.size());
+        while (i.hasPrevious()) {
+            QueryServiceResponse.Message w = i.previous();
+            SQLWarning sw = new SQLWarning(w.msg, null, w.code);
+            if (sqlWarning != null) {
+                sw.setNextWarning(sqlWarning);
+            }
+            sqlWarning = sw;
+        }
+        return sqlWarning;
+    }
+
+    List<ADBColumn> getColumns(QueryServiceResponse response) throws SQLException {
+        if (isExplainOnly(response)) {
+            return Collections.singletonList(new ADBColumn(EXPLAIN_ONLY_RESULT_COLUMN_NAME, ADBDatatype.STRING, false));
+        }
+        QueryServiceResponse.Signature signature = response.signature;
+        if (signature == null) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        List<String> nameList = signature.name;
+        List<String> typeList = signature.type;
+        if (nameList == null || nameList.isEmpty() || typeList == null || typeList.isEmpty()) {
+            throw getErrorReporter().errorBadResultSignature();
+        }
+        int count = nameList.size();
+        List<ADBColumn> result = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            String columnName = nameList.get(i);
+            String typeName = typeList.get(i);
+            boolean optional = false;
+            if (typeName.endsWith(OPTIONAL_TYPE_SUFFIX)) {
+                optional = true;
+                typeName = typeName.substring(0, typeName.length() - OPTIONAL_TYPE_SUFFIX.length());
+            }
+            ADBDatatype columnType = ADBDatatype.findByTypeName(typeName);
+            if (columnType == null) {
+                throw getErrorReporter().errorBadResultSignature();
+            }
+            result.add(new ADBColumn(columnName, columnType, optional));
+        }
+        return result;
+    }
+
+    boolean isExplainOnly(QueryServiceResponse response) {
+        return response.plans != null && Boolean.TRUE.equals(response.plans.explainOnly);
+    }
+
+    int getStatementParameterCount(QueryServiceResponse response) throws SQLException {
+        QueryServiceResponse.Plans plans = response.plans;
+        if (plans == null) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        if (plans.statementParameters == null) {
+            return 0;
+        }
+        int paramPos = 0;
+        for (Object param : plans.statementParameters) {
+            if (param instanceof Number) {
+                paramPos = Math.max(paramPos, ((Number) param).intValue());
+            } else {
+                throw getErrorReporter().errorParameterNotSupported(String.valueOf(param));
+            }
+        }
+        return paramPos;
+    }
+
+    JsonParser createJsonParser(JsonNode node) {
+        return driverContext.genericObjectReader.treeAsTokens(node);
+    }
+
+    ADBErrorReporter getErrorReporter() {
+        return driverContext.errorReporter;
+    }
+
+    Logger getLogger() {
+        return driverContext.logger;
+    }
+
+    static ObjectMapper createObjectMapper() {
+        ObjectMapper om = new ObjectMapper();
+        om.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE);
+        // serialization
+        om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+
+        // deserialization
+        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        om.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
+        om.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
+        return om;
+    }
+
+    private static void closeQuietly(Exception mainExc, java.io.Closeable... closeableList) {
+        for (Closeable closeable : closeableList) {
+            if (closeable != null) {
+                try {
+                    closeable.close();
+                } catch (IOException e) {
+                    if (mainExc != null) {
+                        mainExc.addSuppressed(e);
+                    }
+                }
+            }
+        }
+    }
+
+    static final class ByteArrayOutputStreamImpl extends ByteArrayOutputStream implements ContentProducer {
+        private ByteArrayOutputStreamImpl(int size) {
+            super(size);
+        }
+    }
+
+    static final class EntityTemplateImpl extends EntityTemplate {
+
+        private final long contentLength;
+
+        private EntityTemplateImpl(ByteArrayOutputStreamImpl baos, ContentType contentType) {
+            super(baos);
+            contentLength = baos.size();
+            setContentType(contentType.toString());
+        }
+
+        @Override
+        public long getContentLength() {
+            return contentLength;
+        }
+    }
+
+    static final class InputStreamWithAttachedResource extends FilterInputStream {
+
+        private final Closeable resource;
+
+        private InputStreamWithAttachedResource(InputStream delegate, Closeable resource) {
+            super(delegate);
+            this.resource = Objects.requireNonNull(resource);
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                super.close();
+            } finally {
+                resource.close();
+            }
+        }
+    }
+
+    public static class QueryServiceResponse {
+
+        public Status status;
+        public Plans plans;
+        public Signature signature;
+        public String handle;
+        public List<?> results; // currently only used for EXPLAIN results
+        public List<Message> errors;
+        public List<Message> warnings;
+
+        public enum Status {
+            RUNNING,
+            SUCCESS,
+            TIMEOUT,
+            FAILED,
+            FATAL
+        }
+
+        public static class Signature {
+            List<String> name;
+            List<String> type;
+        }
+
+        public static class Plans {
+            StatementCategory statementCategory;
+            List<Object> statementParameters;
+            Boolean explainOnly;
+        }
+
+        public static class Message {
+            int code;
+            String msg;
+        }
+
+        public enum StatementCategory {
+            QUERY,
+            UPDATE,
+            DDL,
+            PROCEDURE
+        }
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSet.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSet.java
new file mode 100644
index 0000000..965855c
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSet.java
@@ -0,0 +1,1536 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLType;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectReader;
+
+final class ADBResultSet extends ADBWrapperSupport implements java.sql.ResultSet {
+
+    static final int RESULT_SET_HOLDABILITY = HOLD_CURSORS_OVER_COMMIT;
+
+    static final int ST_BEFORE_FIRST = 0;
+    static final int ST_NEXT = 1;
+    static final int ST_AFTER_LAST = 2;
+
+    // lifecycle
+    final AtomicBoolean closed = new AtomicBoolean(false);
+
+    // metadata
+    final ADBResultSetMetaData metadata;
+
+    // navigation
+    final JsonParser rowParser;
+    final boolean rowParserOwnsResources;
+    final long maxRows;
+
+    int state;
+    long rowNumber;
+    ADBRowStore rowStore;
+    ObjectReader complexColumnReader;
+    int columnIndexOfLatestGet;
+
+    // Lifecycle
+
+    ADBResultSet(ADBResultSetMetaData metadata, JsonParser rowParser, boolean rowParserOwnsResources, long maxRows) {
+        this.metadata = Objects.requireNonNull(metadata);
+        this.rowParser = Objects.requireNonNull(rowParser);
+        this.rowParserOwnsResources = rowParserOwnsResources;
+        this.maxRows = maxRows;
+        this.state = ST_BEFORE_FIRST;
+    }
+
+    @Override
+    public void close() throws SQLException {
+        closeImpl(true);
+    }
+
+    void closeImpl(boolean notifyStatement) throws SQLException {
+        boolean wasClosed = closed.getAndSet(true);
+        if (wasClosed) {
+            return;
+        }
+        try {
+            rowParser.close();
+        } catch (IOException e) {
+            throw getErrorReporter().errorClosingResource(e);
+        } finally {
+            if (notifyStatement) {
+                metadata.statement.deregisterResultSet(this);
+            }
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closed.get();
+    }
+
+    private void checkClosed() throws SQLException {
+        if (isClosed()) {
+            throw getErrorReporter().errorObjectClosed(ResultSet.class);
+        }
+    }
+
+    // Metadata
+
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        checkClosed();
+        return metadata;
+    }
+
+    // Navigation
+
+    @Override
+    public boolean next() throws SQLException {
+        checkClosed();
+        try {
+            switch (state) {
+                case ST_BEFORE_FIRST:
+                    JsonToken token = rowParser.hasCurrentToken() ? rowParser.currentToken() : rowParser.nextToken();
+                    if (token != JsonToken.START_ARRAY) {
+                        throw getErrorReporter().errorInProtocol(String.valueOf(token));
+                    }
+                    initRowStore();
+                    state = ST_NEXT;
+                    // fall thru to ST_NEXT
+                case ST_NEXT:
+                    token = rowParser.nextToken();
+                    switch (token) {
+                        case START_OBJECT:
+                            if (maxRows > 0 && rowNumber == maxRows) {
+                                state = ST_AFTER_LAST;
+                                return false;
+                            } else {
+                                readRow();
+                                rowNumber++;
+                                return true;
+                            }
+                        case END_ARRAY:
+                            state = ST_AFTER_LAST;
+                            return false;
+                        default:
+                            throw getErrorReporter().errorInProtocol(String.valueOf(token));
+                    }
+                case ST_AFTER_LAST:
+                    return false;
+                default:
+                    throw new IllegalStateException(String.valueOf(state));
+            }
+        } catch (JsonProcessingException e) {
+            throw getErrorReporter().errorInProtocol(e);
+        } catch (IOException e) {
+            throw getErrorReporter().errorInConnection(e);
+        }
+    }
+
+    private void initRowStore() {
+        rowStore = createRowStore(metadata.getColumnCount());
+    }
+
+    protected ADBRowStore createRowStore(int columnCount) {
+        return new ADBRowStore(this, columnCount);
+    }
+
+    private void readRow() throws SQLException {
+        rowStore.reset();
+        columnIndexOfLatestGet = -1;
+        if (rowParser.currentToken() != JsonToken.START_OBJECT) {
+            throw new IllegalStateException();
+        }
+        try {
+            while (rowParser.nextToken() == JsonToken.FIELD_NAME) {
+                String fieldName = rowParser.getCurrentName();
+                int columnIndex = metadata.findColumnIndexByName(fieldName);
+                boolean isKnownColumn = columnIndex >= 0;
+                ADBColumn column = isKnownColumn ? metadata.getColumnByIndex(columnIndex) : null;
+
+                switch (rowParser.nextToken()) {
+                    case VALUE_NULL:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.NULL);
+                            rowStore.putNullColumn(columnIndex);
+                        }
+                        break;
+                    case VALUE_TRUE:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.BOOLEAN);
+                            rowStore.putBooleanColumn(columnIndex, true);
+                        }
+                        break;
+                    case VALUE_FALSE:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.BOOLEAN);
+                            rowStore.putBooleanColumn(columnIndex, false);
+                        }
+                        break;
+                    case VALUE_NUMBER_INT:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.BIGINT);
+                            long v = rowParser.getLongValue();
+                            rowStore.putInt64Column(columnIndex, v);
+                        }
+                        break;
+                    case VALUE_STRING:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.STRING);
+                            char[] textChars = rowParser.getTextCharacters();
+                            int textOffset = rowParser.getTextOffset();
+                            int textLength = rowParser.getTextLength();
+                            rowStore.putColumn(columnIndex, textChars, textOffset, textLength);
+                        }
+                        break;
+                    case START_OBJECT:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.OBJECT);
+                            Map<?, ?> valueMap = getComplexColumnReader().readValue(rowParser, Map.class);
+                            rowStore.putRecordColumn(columnIndex, valueMap);
+                        } else {
+                            rowParser.skipChildren();
+                        }
+                        break;
+                    case START_ARRAY:
+                        if (isKnownColumn) {
+                            typeCheck(column, ADBDatatype.ARRAY);
+                            List<?> valueList = getComplexColumnReader().readValue(rowParser, List.class);
+                            rowStore.putArrayColumn(columnIndex, valueList);
+                        } else {
+                            rowParser.skipChildren();
+                        }
+                        break;
+                    default:
+                        throw getErrorReporter().errorInProtocol(String.valueOf(rowParser.currentToken()));
+                }
+            }
+        } catch (JsonProcessingException e) {
+            throw getErrorReporter().errorInProtocol(e);
+        } catch (IOException e) {
+            throw getErrorReporter().errorInConnection(e);
+        }
+    }
+
+    private void typeCheck(ADBColumn column, ADBDatatype parsedType) throws SQLException {
+        ADBDatatype columnType = column.getType();
+
+        boolean typeMatch;
+        switch (parsedType) {
+            case NULL:
+                typeMatch = column.isOptional();
+                break;
+            case STRING:
+                // special handling for parsed 'string' because it can contain any primitive type.
+                // we only need to check that the expected type is not derived (i.e primitive/null/missing/any)
+                typeMatch = !columnType.isDerived();
+                break;
+            case ARRAY:
+                typeMatch = columnType == ADBDatatype.ANY || columnType.isList();
+                break;
+            case BOOLEAN:
+            case BIGINT:
+            case OBJECT:
+                typeMatch = columnType == ADBDatatype.ANY || columnType == parsedType;
+                break;
+            default:
+                // unexpected
+                throw getErrorReporter().errorInProtocol(parsedType.toString());
+        }
+        if (!typeMatch) {
+            throw getErrorReporter().errorUnexpectedColumnValue(parsedType, column.getName());
+        }
+    }
+
+    @Override
+    public void beforeFirst() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public void afterLast() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean first() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean last() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean previous() throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean relative(int rows) throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean absolute(int row) throws SQLException {
+        checkClosed();
+        throw getErrorReporter().errorIncompatibleMode("FORWARD_ONLY");
+    }
+
+    @Override
+    public boolean isBeforeFirst() {
+        return state == ST_BEFORE_FIRST;
+    }
+
+    @Override
+    public boolean isAfterLast() {
+        return state == ST_AFTER_LAST;
+    }
+
+    @Override
+    public boolean isFirst() {
+        return state == ST_NEXT && rowNumber == 1;
+    }
+
+    @Override
+    public boolean isLast() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "isLast");
+    }
+
+    @Override
+    public int getRow() throws SQLException {
+        checkClosed();
+        return (int) rowNumber;
+    }
+
+    private void checkCursorPosition() throws SQLException {
+        if (state != ST_NEXT) {
+            throw getErrorReporter().errorNoCurrentRow();
+        }
+    }
+
+    private ObjectReader getComplexColumnReader() {
+        if (complexColumnReader == null) {
+            ADBDriverContext ctx = metadata.statement.connection.protocol.driverContext;
+            ADBRowStore tmpStore = createRowStore(1);
+            complexColumnReader = tmpStore.createComplexColumnObjectReader(ctx.admFormatObjectReader);
+        }
+        return complexColumnReader;
+    }
+
+    // Column accessors
+
+    @Override
+    public int findColumn(String columnLabel) throws SQLException {
+        checkClosed();
+        int columnIndex = metadata.findColumnIndexByName(columnLabel);
+        if (columnIndex < 0) {
+            throw getErrorReporter().errorColumnNotFound(columnLabel);
+        }
+        return columnIndex + 1;
+    }
+
+    // Column accessors: basic types
+
+    private int fetchColumnIndex(int columnNumber) throws SQLException {
+        if (columnNumber < 1 || columnNumber > metadata.getColumnCount()) {
+            throw getErrorReporter().errorColumnNotFound(String.valueOf(columnNumber));
+        }
+        return columnNumber - 1;
+    }
+
+    private int fetchColumnIndex(String columnLabel) throws SQLException {
+        int columnIndex = metadata.findColumnIndexByName(columnLabel);
+        if (columnIndex < 0) {
+            throw getErrorReporter().errorColumnNotFound(columnLabel);
+        }
+        return columnIndex;
+    }
+
+    @Override
+    public boolean wasNull() throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        if (columnIndexOfLatestGet < 0) {
+            return false;
+        }
+        ADBDatatype columnValueType = rowStore.getColumnType(columnIndexOfLatestGet);
+        return columnValueType == ADBDatatype.NULL || columnValueType == ADBDatatype.MISSING;
+    }
+
+    @Override
+    public boolean getBoolean(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBooleanImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public boolean getBoolean(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBooleanImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private boolean getBooleanImpl(int columnIndex) throws SQLException {
+        boolean v = rowStore.getBoolean(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public byte getByte(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getByteImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public byte getByte(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getByteImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private byte getByteImpl(int columnIndex) throws SQLException {
+        byte v = rowStore.getByte(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public short getShort(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getShortImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public short getShort(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getShortImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private short getShortImpl(int columnIndex) throws SQLException {
+        short v = rowStore.getShort(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public int getInt(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getIntImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public int getInt(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getIntImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private int getIntImpl(int columnIndex) throws SQLException {
+        int v = rowStore.getInt(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public long getLong(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getLongImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public long getLong(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getLongImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private long getLongImpl(int columnIndex) throws SQLException {
+        long v = rowStore.getLong(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public float getFloat(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getFloatImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public float getFloat(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getFloatImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private float getFloatImpl(int columnIndex) throws SQLException {
+        float v = rowStore.getFloat(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public double getDouble(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDoubleImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public double getDouble(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDoubleImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private double getDoubleImpl(int columnIndex) throws SQLException {
+        double v = rowStore.getDouble(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBigDecimalImpl(fetchColumnIndex(columnNumber), false, -1);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(int columnNumber, int scale) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBigDecimalImpl(fetchColumnIndex(columnNumber), true, scale);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBigDecimalImpl(fetchColumnIndex(columnLabel), false, -1);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBigDecimalImpl(fetchColumnIndex(columnLabel), true, scale);
+    }
+
+    private BigDecimal getBigDecimalImpl(int columnIndex, boolean setScale, int scale) throws SQLException {
+        BigDecimal v = rowStore.getBigDecimal(columnIndex, setScale, scale);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Date getDate(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDateImpl(fetchColumnIndex(columnNumber), null);
+    }
+
+    @Override
+    public Date getDate(int columnNumber, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDateImpl(fetchColumnIndex(columnNumber), cal);
+    }
+
+    @Override
+    public Date getDate(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDateImpl(fetchColumnIndex(columnLabel), null);
+    }
+
+    @Override
+    public Date getDate(String columnLabel, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getDateImpl(fetchColumnIndex(columnLabel), cal);
+    }
+
+    private Date getDateImpl(int columnIndex, Calendar cal) throws SQLException {
+        Date v = rowStore.getDate(columnIndex, cal);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Time getTime(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimeImpl(fetchColumnIndex(columnNumber), null);
+    }
+
+    @Override
+    public Time getTime(int columnNumber, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimeImpl(fetchColumnIndex(columnNumber), cal);
+    }
+
+    @Override
+    public Time getTime(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimeImpl(fetchColumnIndex(columnLabel), null);
+    }
+
+    @Override
+    public Time getTime(String columnLabel, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimeImpl(fetchColumnIndex(columnLabel), cal);
+    }
+
+    private Time getTimeImpl(int columnIndex, Calendar cal) throws SQLException {
+        Time v = rowStore.getTime(columnIndex, cal);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Timestamp getTimestamp(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimestampImpl(fetchColumnIndex(columnNumber), null);
+    }
+
+    @Override
+    public Timestamp getTimestamp(int columnNumber, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimestampImpl(fetchColumnIndex(columnNumber), cal);
+    }
+
+    @Override
+    public Timestamp getTimestamp(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimestampImpl(fetchColumnIndex(columnLabel), null);
+    }
+
+    @Override
+    public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getTimestampImpl(fetchColumnIndex(columnLabel), cal);
+    }
+
+    private Timestamp getTimestampImpl(int columnIndex, Calendar cal) throws SQLException {
+        Timestamp v = rowStore.getTimestamp(columnIndex, cal);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public String getString(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getStringImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public String getString(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getStringImpl(fetchColumnIndex(columnLabel));
+    }
+
+    @Override
+    public String getNString(int columnNumber) throws SQLException {
+        return getString(columnNumber);
+    }
+
+    @Override
+    public String getNString(String columnLabel) throws SQLException {
+        return getString(columnLabel);
+    }
+
+    private String getStringImpl(int columnIndex) throws SQLException {
+        String v = rowStore.getString(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public byte[] getBytes(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBytesImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public byte[] getBytes(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBytesImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private byte[] getBytesImpl(int columnIndex) throws SQLException {
+        byte[] v = rowStore.getBinary(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    // Column accessor: Generic (getObject)
+
+    @Override
+    public Object getObject(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getObjectImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public Object getObject(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getObjectImpl(fetchColumnIndex(columnLabel));
+    }
+
+    @Override
+    public <T> T getObject(int columnNumber, Class<T> type) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        if (type == null) {
+            throw getErrorReporter().errorParameterValueNotSupported("type");
+        }
+        return getObjectImpl(fetchColumnIndex(columnNumber), type);
+    }
+
+    @Override
+    public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        if (type == null) {
+            throw getErrorReporter().errorParameterValueNotSupported("type");
+        }
+        return getObjectImpl(fetchColumnIndex(columnLabel), type);
+    }
+
+    private Object getObjectImpl(int columnIndex) throws SQLException {
+        ADBColumn column = metadata.getColumnByIndex(columnIndex);
+        return getObjectImpl(columnIndex, column.getType().getJavaClass());
+    }
+
+    private <T> T getObjectImpl(int columnIndex, Class<T> type) throws SQLException {
+        T v = rowStore.getObject(columnIndex, type);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getObject");
+    }
+
+    @Override
+    public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getObject");
+    }
+
+    // Column accessors: streams
+
+    @Override
+    public InputStream getBinaryStream(int columnIndex) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBinaryStreamImpl(fetchColumnIndex(columnIndex));
+    }
+
+    @Override
+    public InputStream getBinaryStream(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getBinaryStreamImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private InputStream getBinaryStreamImpl(int columnIndex) throws SQLException {
+        InputStream v = rowStore.getInputStream(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Reader getCharacterStream(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getCharacterStreamImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public Reader getCharacterStream(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getCharacterStreamImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private Reader getCharacterStreamImpl(int columnIndex) throws SQLException {
+        Reader v = rowStore.getCharacterStream(columnIndex);
+        columnIndexOfLatestGet = columnIndex;
+        return v;
+    }
+
+    @Override
+    public Reader getNCharacterStream(int columnIndex) throws SQLException {
+        return getCharacterStream(columnIndex);
+    }
+
+    @Override
+    public Reader getNCharacterStream(String columnLabel) throws SQLException {
+        return getCharacterStream(columnLabel);
+    }
+
+    @Override
+    public InputStream getAsciiStream(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getAsciiStreamImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public InputStream getAsciiStream(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getAsciiStreamImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private InputStream getAsciiStreamImpl(int columnIndex) throws SQLException {
+        String value = getString(columnIndex);
+        return value != null ? new ByteArrayInputStream(value.getBytes(StandardCharsets.US_ASCII)) : null;
+    }
+
+    @Override
+    public InputStream getUnicodeStream(int columnNumber) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getUnicodeStreamImpl(fetchColumnIndex(columnNumber));
+    }
+
+    @Override
+    public InputStream getUnicodeStream(String columnLabel) throws SQLException {
+        checkClosed();
+        checkCursorPosition();
+        return getUnicodeStreamImpl(fetchColumnIndex(columnLabel));
+    }
+
+    private InputStream getUnicodeStreamImpl(int columnIndex) throws SQLException {
+        String value = getString(columnIndex);
+        return value != null ? new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_16)) : null;
+    }
+
+    // Column accessors: unsupported
+
+    @Override
+    public Ref getRef(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getRef");
+    }
+
+    @Override
+    public Ref getRef(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getRef");
+    }
+
+    @Override
+    public RowId getRowId(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getRowId");
+    }
+
+    @Override
+    public RowId getRowId(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getRowId");
+    }
+
+    @Override
+    public URL getURL(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getURL");
+    }
+
+    @Override
+    public URL getURL(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getURL");
+    }
+
+    // Column accessors: unsupported - LOB, Array, SQLXML
+
+    @Override
+    public Array getArray(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getArray");
+    }
+
+    @Override
+    public Array getArray(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getArray");
+    }
+
+    @Override
+    public Blob getBlob(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getBlob");
+    }
+
+    @Override
+    public Blob getBlob(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getBlob");
+    }
+
+    @Override
+    public Clob getClob(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getClob");
+    }
+
+    @Override
+    public Clob getClob(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getClob");
+    }
+
+    @Override
+    public NClob getNClob(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getNClob");
+    }
+
+    @Override
+    public NClob getNClob(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getNClob");
+    }
+
+    @Override
+    public SQLXML getSQLXML(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getSQLXML");
+    }
+
+    @Override
+    public SQLXML getSQLXML(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "getSQLXML");
+    }
+
+    // Updates (unsupported)
+
+    // Column setters
+
+    @Override
+    public void updateArray(int columnIndex, Array x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateArray");
+    }
+
+    @Override
+    public void updateArray(String columnLabel, Array x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateArray");
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateAsciiStream");
+    }
+
+    @Override
+    public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBigDecimal");
+    }
+
+    @Override
+    public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBigDecimal");
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBinaryStream");
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, Blob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, Blob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBlob");
+    }
+
+    @Override
+    public void updateBoolean(int columnIndex, boolean x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBoolean");
+    }
+
+    @Override
+    public void updateBoolean(String columnLabel, boolean x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBoolean");
+    }
+
+    @Override
+    public void updateByte(int columnIndex, byte x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateByte");
+    }
+
+    @Override
+    public void updateByte(String columnLabel, byte x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateByte");
+    }
+
+    @Override
+    public void updateBytes(int columnIndex, byte[] x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBytes");
+    }
+
+    @Override
+    public void updateBytes(String columnLabel, byte[] x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateBytes");
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateCharacterStream");
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Clob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Clob x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateClob(int columnIndex, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateClob(String columnLabel, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateClob");
+    }
+
+    @Override
+    public void updateDate(int columnIndex, Date x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateDate");
+    }
+
+    @Override
+    public void updateDate(String columnLabel, Date x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateDate");
+    }
+
+    @Override
+    public void updateDouble(int columnIndex, double x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateDouble");
+    }
+
+    @Override
+    public void updateDouble(String columnLabel, double x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateDouble");
+    }
+
+    @Override
+    public void updateFloat(int columnIndex, float x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateFloat");
+    }
+
+    @Override
+    public void updateFloat(String columnLabel, float x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateFloat");
+    }
+
+    @Override
+    public void updateInt(int columnIndex, int x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateInt");
+    }
+
+    @Override
+    public void updateInt(String columnLabel, int x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateInt");
+    }
+
+    @Override
+    public void updateLong(int columnIndex, long x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateLong");
+    }
+
+    @Override
+    public void updateLong(String columnLabel, long x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateLong");
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNCharacterStream");
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNCharacterStream");
+    }
+
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNCharacterStream");
+    }
+
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNCharacterStream");
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNClob(int columnIndex, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNClob(String columnLabel, Reader reader) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNClob");
+    }
+
+    @Override
+    public void updateNString(int columnIndex, String nString) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNString");
+    }
+
+    @Override
+    public void updateNString(String columnLabel, String nString) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNString");
+    }
+
+    @Override
+    public void updateNull(int columnIndex) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNull");
+    }
+
+    @Override
+    public void updateNull(String columnLabel) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateNull");
+    }
+
+    @Override
+    public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(int columnIndex, Object x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength)
+            throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(String columnLabel, Object x, SQLType targetSqlType) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateObject(String columnLabel, Object x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateObject");
+    }
+
+    @Override
+    public void updateRef(int columnIndex, Ref x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateRef");
+    }
+
+    @Override
+    public void updateRef(String columnLabel, Ref x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateRef");
+    }
+
+    @Override
+    public void updateRowId(int columnIndex, RowId x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateRowId");
+    }
+
+    @Override
+    public void updateRowId(String columnLabel, RowId x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateRowId");
+    }
+
+    @Override
+    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateSQLXML");
+    }
+
+    @Override
+    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateSQLXML");
+    }
+
+    @Override
+    public void updateShort(int columnIndex, short x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateShort");
+    }
+
+    @Override
+    public void updateShort(String columnLabel, short x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateShort");
+    }
+
+    @Override
+    public void updateString(int columnIndex, String x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateString");
+    }
+
+    @Override
+    public void updateString(String columnLabel, String x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateString");
+    }
+
+    @Override
+    public void updateTime(int columnIndex, Time x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateTime");
+    }
+
+    @Override
+    public void updateTime(String columnLabel, Time x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateTime");
+    }
+
+    @Override
+    public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateTimestamp");
+    }
+
+    @Override
+    public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateTimestamp");
+    }
+
+    // Update navigation and state (unsupported)
+
+    @Override
+    public void insertRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "insertRow");
+    }
+
+    @Override
+    public void updateRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "updateRow");
+    }
+
+    @Override
+    public void deleteRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "deleteRow");
+    }
+
+    @Override
+    public void refreshRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "refreshRow");
+    }
+
+    @Override
+    public void moveToInsertRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "moveToInsertRow");
+    }
+
+    @Override
+    public void moveToCurrentRow() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "moveToCurrentRow");
+    }
+
+    @Override
+    public boolean rowInserted() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "rowInserted");
+    }
+
+    @Override
+    public boolean rowUpdated() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "rowUpdated");
+    }
+
+    @Override
+    public boolean rowDeleted() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "rowDeleted");
+
+    }
+
+    @Override
+    public void cancelRowUpdates() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(ResultSet.class, "cancelRowUpdates");
+    }
+
+    // Errors and warnings
+
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        checkClosed();
+        return null;
+    }
+
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkClosed();
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return metadata.getErrorReporter();
+    }
+
+    // Ownership
+
+    @Override
+    public Statement getStatement() throws SQLException {
+        checkClosed();
+        return metadata.statement.getResultSetStatement(this);
+    }
+
+    // Cursor - related
+
+    @Override
+    public String getCursorName() throws SQLException {
+        checkClosed();
+        return "";
+    }
+
+    @Override
+    public int getType() throws SQLException {
+        checkClosed();
+        return TYPE_FORWARD_ONLY;
+    }
+
+    @Override
+    public int getConcurrency() throws SQLException {
+        checkClosed();
+        return ResultSet.CONCUR_READ_ONLY;
+    }
+
+    @Override
+    public int getHoldability() throws SQLException {
+        checkClosed();
+        return RESULT_SET_HOLDABILITY;
+    }
+
+    @Override
+    public int getFetchDirection() throws SQLException {
+        checkClosed();
+        return FETCH_FORWARD;
+    }
+
+    @Override
+    public void setFetchDirection(int direction) throws SQLException {
+        checkClosed();
+        if (direction != ResultSet.FETCH_FORWARD) {
+            throw getErrorReporter().errorParameterValueNotSupported("direction");
+        }
+    }
+
+    @Override
+    public int getFetchSize() throws SQLException {
+        checkClosed();
+        return 1;
+    }
+
+    @Override
+    public void setFetchSize(int rows) throws SQLException {
+        checkClosed();
+        // ignore value
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSetMetaData.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSetMetaData.java
new file mode 100644
index 0000000..67dd217
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBResultSetMetaData.java
@@ -0,0 +1,194 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+final class ADBResultSetMetaData extends ADBWrapperSupport implements ResultSetMetaData {
+
+    final ADBStatement statement;
+
+    private final List<ADBColumn> columns;
+
+    private final Map<String, Integer> indexByName;
+
+    ADBResultSetMetaData(ADBStatement statement, List<ADBColumn> columns) {
+        this.statement = Objects.requireNonNull(statement);
+        this.columns = columns != null ? columns : Collections.emptyList();
+        this.indexByName = createIndexByName(this.columns);
+    }
+
+    @Override
+    public int getColumnCount() {
+        return columns.size();
+    }
+
+    @Override
+    public String getColumnName(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).getName();
+    }
+
+    @Override
+    public String getColumnLabel(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).getName();
+    }
+
+    @Override
+    public int getColumnType(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).getType().getJdbcType().getVendorTypeNumber();
+    }
+
+    @Override
+    public String getColumnTypeName(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).getType().getTypeName();
+    }
+
+    @Override
+    public String getColumnClassName(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).getType().getJavaClass().getName();
+    }
+
+    @Override
+    public int getColumnDisplaySize(int columnNumber) {
+        // TODO:based on type
+        return 1;
+    }
+
+    @Override
+    public int getPrecision(int columnNumber) {
+        // TODO:based on type
+        return 0;
+    }
+
+    @Override
+    public int getScale(int columnNumber) {
+        return 0;
+    }
+
+    @Override
+    public boolean isAutoIncrement(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public boolean isCaseSensitive(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public boolean isCurrency(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public int isNullable(int columnNumber) throws SQLException {
+        return getColumnByNumber(columnNumber).isOptional() ? columnNullable : columnNoNulls;
+    }
+
+    @Override
+    public boolean isSearchable(int columnNumber) {
+        return true;
+    }
+
+    @Override
+    public boolean isSigned(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public boolean isReadOnly(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public boolean isWritable(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public boolean isDefinitelyWritable(int columnNumber) {
+        return false;
+    }
+
+    @Override
+    public String getCatalogName(int columnNumber) {
+        return "";
+    }
+
+    @Override
+    public String getSchemaName(int columnNumber) {
+        return "";
+    }
+
+    @Override
+    public String getTableName(int columnNumber) {
+        return "";
+    }
+
+    // Helpers
+
+    private ADBColumn getColumnByNumber(int columnNumber) throws SQLException {
+        return getColumnByIndex(toColumnIndex(columnNumber));
+    }
+
+    private int toColumnIndex(int columnNumber) throws SQLException {
+        boolean ok = 0 < columnNumber && columnNumber <= columns.size();
+        if (!ok) {
+            throw getErrorReporter().errorParameterValueNotSupported("columnNumber");
+        }
+        return columnNumber - 1;
+    }
+
+    ADBColumn getColumnByIndex(int idx) {
+        return columns.get(idx);
+    }
+
+    int findColumnIndexByName(String columnName) {
+        Integer idx = indexByName.get(columnName);
+        return idx != null ? idx : -1;
+    }
+
+    private static Map<String, Integer> createIndexByName(List<ADBColumn> columns) {
+        int n = columns.size();
+        switch (n) {
+            case 0:
+                return Collections.emptyMap();
+            case 1:
+                return Collections.singletonMap(columns.get(0).getName(), 0);
+            default:
+                Map<String, Integer> m = new HashMap<>();
+                for (int i = 0; i < n; i++) {
+                    m.put(columns.get(i).getName(), i);
+                }
+                return m;
+        }
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return statement.getErrorReporter();
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBRowStore.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBRowStore.java
new file mode 100644
index 0000000..da9c5bd
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBRowStore.java
@@ -0,0 +1,1191 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+final class ADBRowStore {
+
+    private static final String ROW_STORE_ATTR_NAME = ADBRowStore.class.getSimpleName();
+
+    private static final int FLOAT_NAN_BITS = Float.floatToIntBits(Float.NaN);
+    private static final int FLOAT_POSITIVE_ZERO_BITS = Float.floatToIntBits(+0.0f);
+    private static final int FLOAT_NEGATIVE_ZERO_BITS = Float.floatToIntBits(-0.0f);
+    private static final long DOUBLE_NAN_BITS = Double.doubleToLongBits(Double.NaN);
+    private static final long DOUBLE_POSITIVE_ZERO_BITS = Double.doubleToLongBits(+0.0d);
+    private static final long DOUBLE_NEGATIVE_ZERO_BITS = Double.doubleToLongBits(-0.0d);
+
+    static final Map<Class<?>, GetObjectFunction> OBJECT_ACCESSORS_ATOMIC = createAtomicObjectAccessorMap();
+
+    static final List<Class<?>> GET_OBJECT_NON_ATOMIC = Arrays.asList(Collection.class, List.class, Map.class);
+
+    private final ADBResultSet resultSet;
+
+    private final ADBDatatype[] columnTypes;
+    private final Object[] objectStore;
+    private final long[] registerStore; // 2 registers per column
+
+    private int parsedLength;
+    private long currentDateChronon;
+    private JsonGenerator jsonGen;
+    private StringWriter jsonGenBuffer;
+
+    ADBRowStore(ADBResultSet resultSet, int initialColumnCount) {
+        this.resultSet = Objects.requireNonNull(resultSet);
+        columnTypes = new ADBDatatype[initialColumnCount];
+        objectStore = new Object[initialColumnCount];
+        registerStore = new long[initialColumnCount * 2];
+    }
+
+    void reset() {
+        Arrays.fill(columnTypes, ADBDatatype.MISSING);
+        Arrays.fill(registerStore, 0);
+        Arrays.fill(objectStore, null);
+    }
+
+    private void setColumnType(int columnIndex, ADBDatatype columnType) {
+        columnTypes[columnIndex] = columnType;
+    }
+
+    ADBDatatype getColumnType(int columnIndex) {
+        return columnTypes[columnIndex];
+    }
+
+    void putColumn(int columnIndex, char[] textChars, int textOffset, int textLength) throws SQLException {
+        byte valueTypeTag = parseTypeTag(textChars, textOffset, textLength);
+        ADBDatatype valueType = ADBDatatype.findByTypeTag(valueTypeTag);
+        if (valueType == null) {
+            throw getErrorReporter().errorUnexpectedType(valueTypeTag);
+        }
+
+        int nonTaggedOffset = textOffset + parsedLength;
+        int nonTaggedLength = textLength - parsedLength;
+        int nonTaggedEnd = nonTaggedOffset + nonTaggedLength; // = textOffset + textLength
+
+        setColumnType(columnIndex, valueType);
+
+        // NULL, BOOLEAN, BIGINT shouldn't normally happen. only handle here for completeness
+
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                // no content
+                break;
+            case BOOLEAN:
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+            case FLOAT:
+            case DOUBLE:
+            case DATE:
+            case TIME:
+            case DATETIME:
+            case YEARMONTHDURATION:
+            case DAYTIMEDURATION:
+                long r0 = parseInt64(textChars, nonTaggedOffset, nonTaggedEnd);
+                setColumnRegisters(columnIndex, r0, 0);
+                break;
+            case STRING:
+                objectStore[columnIndex] = new String(textChars, nonTaggedOffset, nonTaggedLength);
+                break;
+            case DURATION:
+                int delimiterOffset = indexOf(ADBProtocol.TEXT_DELIMITER, textChars, nonTaggedOffset, nonTaggedEnd);
+                if (delimiterOffset < 0 || delimiterOffset == nonTaggedEnd - 1) {
+                    throw getErrorReporter().errorInProtocol();
+                }
+                r0 = parseInt64(textChars, nonTaggedOffset, delimiterOffset);
+                long r1 = parseInt64(textChars, delimiterOffset + 1, nonTaggedEnd);
+                setColumnRegisters(columnIndex, r0, r1);
+                break;
+            case UUID:
+                // TODO: better encoding as 2 longs?
+                objectStore[columnIndex] = UUID.fromString(new String(textChars, nonTaggedOffset, nonTaggedLength));
+                break;
+            case OBJECT:
+            case ARRAY:
+            case MULTISET:
+                // Unexpected (shouldn't be called)
+                throw new IllegalArgumentException(String.valueOf(valueType));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    void putNullColumn(int columnIndex) {
+        setColumnType(columnIndex, ADBDatatype.NULL);
+    }
+
+    void putBooleanColumn(int columnIndex, boolean value) {
+        setColumnType(columnIndex, ADBDatatype.BOOLEAN);
+        setColumnRegisters(columnIndex, value ? 1 : 0, 0);
+    }
+
+    void putInt64Column(int columnIndex, long value) {
+        setColumnType(columnIndex, ADBDatatype.BIGINT);
+        setColumnRegisters(columnIndex, value, 0);
+    }
+
+    void putArrayColumn(int columnIndex, List<?> value) {
+        setColumnType(columnIndex, ADBDatatype.ARRAY);
+        objectStore[columnIndex] = Objects.requireNonNull(value);
+    }
+
+    void putRecordColumn(int columnIndex, Map<?, ?> value) {
+        setColumnType(columnIndex, ADBDatatype.OBJECT);
+        objectStore[columnIndex] = Objects.requireNonNull(value);
+    }
+
+    private void setColumnRegisters(int columnIndex, long r0, long r1) {
+        int registerPos = columnIndex * 2;
+        registerStore[registerPos] = r0;
+        registerStore[++registerPos] = r1;
+    }
+
+    private long getColumnRegister(int columnIndex, int registerIndex) {
+        int registerPos = columnIndex * 2;
+        switch (registerIndex) {
+            case 0:
+                break;
+            case 1:
+                registerPos++;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        return registerStore[registerPos];
+    }
+
+    private boolean getColumnRegisterAsBoolean(int columnIndex, int registerIndex) {
+        return getColumnRegister(columnIndex, registerIndex) != 0;
+    }
+
+    private byte getColumnRegisterAsByte(int columnIndex, int registerIndex) {
+        return (byte) getColumnRegister(columnIndex, registerIndex);
+    }
+
+    private short getColumnRegisterAsShort(int columnIndex, int registerIndex) {
+        return (short) getColumnRegister(columnIndex, registerIndex);
+    }
+
+    private int getColumnRegisterAsInt(int columnIndex, int registerIndex) {
+        return (int) getColumnRegister(columnIndex, registerIndex);
+    }
+
+    private float getColumnRegisterAsFloat(int columnIndex, int registerIndex) {
+        return Float.intBitsToFloat(getColumnRegisterAsFloatBits(columnIndex, registerIndex));
+    }
+
+    private boolean isColumnRegisterZeroOrNanFloat(int columnIndex, int registerIndex) {
+        int bits = getColumnRegisterAsFloatBits(columnIndex, registerIndex);
+        return bits == FLOAT_POSITIVE_ZERO_BITS || bits == FLOAT_NEGATIVE_ZERO_BITS || bits == FLOAT_NAN_BITS;
+    }
+
+    private int getColumnRegisterAsFloatBits(int columnIndex, int registerIndex) {
+        return getColumnRegisterAsInt(columnIndex, registerIndex);
+    }
+
+    private double getColumnRegisterAsDouble(int columnIndex, int registerIndex) {
+        return Double.longBitsToDouble(getColumnRegisterAsDoubleBits(columnIndex, registerIndex));
+    }
+
+    private boolean isColumnRegisterZeroOrNanDouble(int columnIndex, int registerIndex) {
+        long bits = getColumnRegisterAsDoubleBits(columnIndex, registerIndex);
+        return bits == DOUBLE_POSITIVE_ZERO_BITS || bits == DOUBLE_NEGATIVE_ZERO_BITS || bits == DOUBLE_NAN_BITS;
+    }
+
+    private long getColumnRegisterAsDoubleBits(int columnIndex, int registerIndex) {
+        return getColumnRegister(columnIndex, registerIndex);
+    }
+
+    private Period getColumnRegisterAsPeriod(int columnIndex, int registerIndex) {
+        return Period.ofMonths((int) getColumnRegister(columnIndex, registerIndex));
+    }
+
+    private Duration getColumnRegisterAsDuration(int columnIndex, int registerIndex) {
+        return Duration.ofMillis((int) getColumnRegister(columnIndex, registerIndex));
+    }
+
+    private Number getNumberFromObjectStore(int columnIndex) {
+        Object o = objectStore[columnIndex];
+        if (o != null) {
+            return (Number) o;
+        }
+        Number n;
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case TINYINT:
+                n = getColumnRegisterAsByte(columnIndex, 0);
+                break;
+            case SMALLINT:
+                n = getColumnRegisterAsShort(columnIndex, 0);
+                break;
+            case INTEGER:
+                n = getColumnRegisterAsInt(columnIndex, 0);
+                break;
+            case BIGINT:
+                n = getColumnRegister(columnIndex, 0);
+                break;
+            case FLOAT:
+                n = getColumnRegisterAsFloat(columnIndex, 0);
+                break;
+            case DOUBLE:
+                n = getColumnRegisterAsDouble(columnIndex, 0);
+                break;
+            default:
+                throw new IllegalArgumentException(String.valueOf(valueType));
+        }
+        objectStore[columnIndex] = n;
+        return n;
+    }
+
+    private String getStringFromObjectStore(int columnIndex) {
+        return (String) objectStore[columnIndex];
+    }
+
+    private UUID getUUIDFromObjectStore(int columnIndex) {
+        return (UUID) objectStore[columnIndex];
+    }
+
+    private Period getPeriodFromObjectStore(int columnIndex) {
+        Object o = objectStore[columnIndex];
+        if (o != null) {
+            return (Period) o;
+        }
+        ADBDatatype valueType = getColumnType(columnIndex);
+        if (valueType != ADBDatatype.YEARMONTHDURATION) {
+            throw new IllegalArgumentException(String.valueOf(valueType));
+        }
+        Period v = getColumnRegisterAsPeriod(columnIndex, 0);
+        objectStore[columnIndex] = v;
+        return v;
+    }
+
+    private Duration getDurationFromObjectStore(int columnIndex) {
+        Object o = objectStore[columnIndex];
+        if (o != null) {
+            return (Duration) o;
+        }
+        ADBDatatype valueType = getColumnType(columnIndex);
+        if (valueType != ADBDatatype.DAYTIMEDURATION) {
+            throw new IllegalArgumentException(String.valueOf(valueType));
+        }
+        Duration v = getColumnRegisterAsDuration(columnIndex, 0);
+        objectStore[columnIndex] = v;
+        return v;
+    }
+
+    private String getISODurationStringFromObjectStore(int columnIndex) {
+        Object o = objectStore[columnIndex];
+        if (o != null) {
+            return (String) o;
+        }
+        ADBDatatype valueType = getColumnType(columnIndex);
+        if (valueType != ADBDatatype.DURATION) {
+            throw new IllegalArgumentException(String.valueOf(valueType));
+        }
+        String v = getColumnRegisterAsPeriod(columnIndex, 0).toString()
+                + getColumnRegisterAsDuration(columnIndex, 1).toString().substring(1);
+        objectStore[columnIndex] = v;
+        return v;
+    }
+
+    boolean getBoolean(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return false;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0);
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return getColumnRegister(columnIndex, 0) != 0;
+            case FLOAT:
+                return !isColumnRegisterZeroOrNanFloat(columnIndex, 0);
+            case DOUBLE:
+                return !isColumnRegisterZeroOrNanDouble(columnIndex, 0);
+            case STRING:
+                return Boolean.parseBoolean(getStringFromObjectStore(columnIndex));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    byte getByte(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return (byte) (getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0);
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return getColumnRegisterAsByte(columnIndex, 0);
+            case FLOAT:
+                return (byte) getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return (byte) getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                return (byte) parseInt64(getStringFromObjectStore(columnIndex));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    short getShort(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return (short) (getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0);
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return getColumnRegisterAsShort(columnIndex, 0);
+            case FLOAT:
+                return (short) getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return (short) getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                return (short) parseInt64(getStringFromObjectStore(columnIndex));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    int getInt(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+            case DATE:
+            case TIME:
+            case YEARMONTHDURATION:
+                return getColumnRegisterAsInt(columnIndex, 0);
+            case FLOAT:
+                return (int) getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return (int) getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                return (int) parseInt64(getStringFromObjectStore(columnIndex));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    long getLong(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+            case DATE:
+            case TIME:
+            case DATETIME:
+            case YEARMONTHDURATION:
+            case DAYTIMEDURATION:
+                return getColumnRegister(columnIndex, 0);
+            case FLOAT:
+                return (long) getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return (long) getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                return parseInt64(getStringFromObjectStore(columnIndex));
+            default:
+                // TODO:support temporal types?
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    float getFloat(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0) ? 1f : 0f;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return getColumnRegister(columnIndex, 0);
+            case FLOAT:
+                return getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return (float) getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                try {
+                    return Float.parseFloat(getStringFromObjectStore(columnIndex));
+                } catch (NumberFormatException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    double getDouble(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return 0;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0) ? 1d : 0d;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return getColumnRegister(columnIndex, 0);
+            case FLOAT:
+                return getColumnRegisterAsFloat(columnIndex, 0);
+            case DOUBLE:
+                return getColumnRegisterAsDouble(columnIndex, 0);
+            case STRING:
+                try {
+                    return Double.parseDouble(getStringFromObjectStore(columnIndex));
+                } catch (NumberFormatException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    BigDecimal getBigDecimal(int columnIndex) throws SQLException {
+        return getBigDecimal(columnIndex, false, 0);
+    }
+
+    @SuppressWarnings("UnpredictableBigDecimalConstructorCall")
+    BigDecimal getBigDecimal(int columnIndex, boolean setScale, int scale) throws SQLException {
+        BigDecimal dec;
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case BOOLEAN:
+                dec = getColumnRegisterAsBoolean(columnIndex, 0) ? BigDecimal.ONE : BigDecimal.ZERO;
+                break;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+            case DATE:
+            case TIME:
+            case DATETIME:
+            case YEARMONTHDURATION:
+            case DAYTIMEDURATION:
+                dec = BigDecimal.valueOf(getColumnRegister(columnIndex, 0));
+                break;
+            case FLOAT:
+                try {
+                    dec = new BigDecimal(getColumnRegisterAsFloat(columnIndex, 0));
+                } catch (NumberFormatException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+                break;
+            case DOUBLE:
+                try {
+                    dec = new BigDecimal(getColumnRegisterAsDouble(columnIndex, 0));
+                } catch (NumberFormatException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+                break;
+            case STRING:
+                try {
+                    dec = new BigDecimal(getStringFromObjectStore(columnIndex));
+                } catch (NumberFormatException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+                break;
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+
+        return setScale ? dec.setScale(scale, RoundingMode.DOWN) : dec;
+    }
+
+    private Date getDate(int columnIndex) throws SQLException {
+        return getDate(columnIndex, null);
+    }
+
+    Date getDate(int columnIndex, Calendar cal) throws SQLException {
+        // TODO:cal is not used
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case DATE:
+                return toDateFromDateChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toDateFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    LocalDate d = LocalDate.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                    return new Date(d.getYear() - 1900, d.getMonthValue() - 1, d.getDayOfMonth());
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    LocalDate getLocalDate(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case DATE:
+                return toLocalDateFromDateChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toLocalDateFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    return LocalDate.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    private Time getTime(int columnIndex) throws SQLException {
+        return getTime(columnIndex, null);
+    }
+
+    Time getTime(int columnIndex, Calendar cal) throws SQLException {
+        // TODO:cal not used
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case TIME:
+                return toTimeFromTimeChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    LocalTime t = LocalTime.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                    return toTimeFromTimeChronon(TimeUnit.NANOSECONDS.toMillis(t.toNanoOfDay()));
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    LocalTime getLocalTime(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case TIME:
+                return toLocalTimeFromTimeChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toLocalTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    return LocalTime.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    private Timestamp getTimestamp(int columnIndex) throws SQLException {
+        return getTimestamp(columnIndex, null);
+    }
+
+    Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
+        //TODO:FIXME:CAL NOT USED
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case DATE:
+                return toTimestampFromDateChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toTimestampFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    Instant i = Instant.parse(getStringFromObjectStore(columnIndex));
+                    long millis0 = TimeUnit.SECONDS.toMillis(i.getEpochSecond());
+                    long millis1 = TimeUnit.NANOSECONDS.toMillis(i.getNano());
+                    return new Timestamp(millis0 + millis1);
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    Instant getInstant(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case DATE:
+                return toInstantFromDateChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toInstantFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case STRING:
+                try {
+                    return Instant.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    Period getPeriod(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case YEARMONTHDURATION:
+                return getPeriodFromObjectStore(columnIndex);
+            case DURATION:
+                return getColumnRegisterAsPeriod(columnIndex, 0);
+            case STRING:
+                try {
+                    return Period.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    Duration getDuration(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case DAYTIMEDURATION:
+                return getDurationFromObjectStore(columnIndex);
+            case DURATION:
+                return getColumnRegisterAsDuration(columnIndex, 1);
+            case STRING:
+                try {
+                    return Duration.parse(getStringFromObjectStore(columnIndex)); // TODO:review
+                } catch (DateTimeParseException e) {
+                    throw getErrorReporter().errorInvalidValueOfType(valueType);
+                }
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    byte[] getBinary(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case STRING:
+                return getStringFromObjectStore(columnIndex).getBytes(StandardCharsets.UTF_8);
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    UUID getUUID(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case UUID:
+                return getUUIDFromObjectStore(columnIndex);
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    String getString(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case BOOLEAN:
+                return Boolean.toString(getColumnRegisterAsBoolean(columnIndex, 0));
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return Long.toString(getColumnRegister(columnIndex, 0));
+            case FLOAT:
+                return Float.toString(getColumnRegisterAsFloat(columnIndex, 0));
+            case DOUBLE:
+                return Double.toString(getColumnRegisterAsDouble(columnIndex, 0));
+            case DATE:
+                return toLocalDateFromDateChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review
+            case TIME:
+                return toLocalTimeFromTimeChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review
+            case DATETIME:
+                return toInstantFromDatetimeChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review
+            case YEARMONTHDURATION:
+                return getPeriodFromObjectStore(columnIndex).toString(); // TODO:review
+            case DAYTIMEDURATION:
+                return getDurationFromObjectStore(columnIndex).toString(); // TODO:review
+            case DURATION:
+                return getISODurationStringFromObjectStore(columnIndex); // TODO:review
+            case STRING:
+                return getStringFromObjectStore(columnIndex);
+            case UUID:
+                return getUUIDFromObjectStore(columnIndex).toString();
+            case OBJECT:
+            case ARRAY:
+                return printAsJson(objectStore[columnIndex]);
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    Reader getCharacterStream(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case STRING:
+                return new StringReader(getStringFromObjectStore(columnIndex));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    InputStream getInputStream(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case STRING:
+                return new ByteArrayInputStream(getStringFromObjectStore(columnIndex).getBytes(StandardCharsets.UTF_8));
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    Object getObject(int columnIndex) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            case BOOLEAN:
+                return getColumnRegisterAsBoolean(columnIndex, 0);
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+            case FLOAT:
+            case DOUBLE:
+                return getNumberFromObjectStore(columnIndex);
+            case DATE:
+                return toDateFromDateChronon(getColumnRegister(columnIndex, 0));
+            case TIME:
+                return toTimeFromTimeChronon(getColumnRegister(columnIndex, 0));
+            case DATETIME:
+                return toTimestampFromDatetimeChronon(getColumnRegister(columnIndex, 0));
+            case YEARMONTHDURATION:
+                return getPeriodFromObjectStore(columnIndex);
+            case DAYTIMEDURATION:
+                return getDurationFromObjectStore(columnIndex);
+            case DURATION:
+                return getISODurationStringFromObjectStore(columnIndex);
+            case STRING:
+                return getStringFromObjectStore(columnIndex);
+            case UUID:
+                return getUUIDFromObjectStore(columnIndex);
+            case OBJECT:
+            case ARRAY:
+                return objectStore[columnIndex]; // TODO:how to make immutable?
+            default:
+                throw getErrorReporter().errorUnexpectedType(valueType);
+        }
+    }
+
+    <T> T getObject(int columnIndex, Class<T> targetType) throws SQLException {
+        ADBDatatype valueType = getColumnType(columnIndex);
+        switch (valueType) {
+            case MISSING:
+            case NULL:
+                return null;
+            default:
+                GetObjectFunction getter = OBJECT_ACCESSORS_ATOMIC.get(targetType);
+                Object v;
+                if (getter != null) {
+                    v = getter.getObject(this, columnIndex);
+                } else if (GET_OBJECT_NON_ATOMIC.contains(targetType)) {
+                    v = getObject(columnIndex);
+                } else {
+                    throw getErrorReporter().errorUnexpectedType(targetType);
+                }
+                return targetType.cast(v);
+        }
+    }
+
+    interface GetObjectFunction {
+        Object getObject(ADBRowStore rowStore, int columnIndex) throws SQLException;
+    }
+
+    private static Map<Class<?>, GetObjectFunction> createAtomicObjectAccessorMap() {
+        Map<Class<?>, GetObjectFunction> map = new HashMap<>();
+        map.put(Boolean.TYPE, ADBRowStore::getBoolean);
+        map.put(Boolean.class, ADBRowStore::getBoolean);
+        map.put(Byte.TYPE, ADBRowStore::getByte);
+        map.put(Byte.class, ADBRowStore::getByte);
+        map.put(Short.TYPE, ADBRowStore::getShort);
+        map.put(Short.class, ADBRowStore::getShort);
+        map.put(Integer.TYPE, ADBRowStore::getInt);
+        map.put(Integer.class, ADBRowStore::getInt);
+        map.put(Long.TYPE, ADBRowStore::getLong);
+        map.put(Long.class, ADBRowStore::getLong);
+        map.put(Float.TYPE, ADBRowStore::getFloat);
+        map.put(Float.class, ADBRowStore::getFloat);
+        map.put(Double.TYPE, ADBRowStore::getDouble);
+        map.put(Double.class, ADBRowStore::getDouble);
+        map.put(BigDecimal.class, ADBRowStore::getBigDecimal);
+        map.put(Date.class, ADBRowStore::getDate);
+        map.put(LocalDate.class, ADBRowStore::getLocalDate);
+        map.put(Time.class, ADBRowStore::getTime);
+        map.put(LocalTime.class, ADBRowStore::getLocalTime);
+        map.put(Timestamp.class, ADBRowStore::getTimestamp);
+        map.put(Instant.class, ADBRowStore::getInstant);
+        map.put(Period.class, ADBRowStore::getPeriod);
+        map.put(Duration.class, ADBRowStore::getDuration);
+        map.put(UUID.class, ADBRowStore::getUUID);
+        map.put(String.class, ADBRowStore::getString);
+        return map;
+    }
+
+    private Date toDateFromDateChronon(long dateChrononInDays) {
+        return new Date(TimeUnit.DAYS.toMillis(dateChrononInDays));
+    }
+
+    private Date toDateFromDatetimeChronon(long datetimeChrononInMillis) {
+        return new Date(datetimeChrononInMillis);
+    }
+
+    private LocalDate toLocalDateFromDateChronon(long dateChrononInDays) {
+        return LocalDate.ofEpochDay(dateChrononInDays);
+    }
+
+    private LocalDate toLocalDateFromDatetimeChronon(long datetimeChrononInMillis) {
+        return LocalDate.ofEpochDay(TimeUnit.MILLISECONDS.toDays(datetimeChrononInMillis));
+    }
+
+    private Time toTimeFromTimeChronon(long timeChrononInMillis) {
+        long datetimeChrononInMillis = getCurrentDateChrononInMillis() + timeChrononInMillis;
+        return new Time(datetimeChrononInMillis);
+    }
+
+    private Time toTimeFromDatetimeChronon(long datetimeChrononInMillis) {
+        return new Time(datetimeChrononInMillis);
+    }
+
+    private LocalTime toLocalTimeFromTimeChronon(long timeChrononInMillis) {
+        return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(timeChrononInMillis));
+    }
+
+    private LocalTime toLocalTimeFromDatetimeChronon(long datetimeChrononInMillis) {
+        return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(datetimeChrononInMillis));
+    }
+
+    private Timestamp toTimestampFromDatetimeChronon(long datetimeChrononInMillis) {
+        return new Timestamp(datetimeChrononInMillis);
+    }
+
+    private Timestamp toTimestampFromDateChronon(long dateChrononInDays) {
+        return new Timestamp(TimeUnit.DAYS.toMillis(dateChrononInDays));
+    }
+
+    private Instant toInstantFromDatetimeChronon(long datetimeChrononInMillis) {
+        return Instant.ofEpochMilli(datetimeChrononInMillis);
+    }
+
+    private Instant toInstantFromDateChronon(long dateChrononInDays) {
+        return Instant.ofEpochMilli(TimeUnit.DAYS.toMillis(dateChrononInDays));
+    }
+
+    private long getCurrentDateChrononInMillis() {
+        if (currentDateChronon == 0) {
+            long chrononOfDay = TimeUnit.DAYS.toMillis(1);
+            currentDateChronon = System.currentTimeMillis() / chrononOfDay * chrononOfDay;
+        }
+        return currentDateChronon;
+    }
+
+    private String printAsJson(Object value) throws SQLException {
+        if (jsonGenBuffer == null) {
+            jsonGenBuffer = new StringWriter();
+            try {
+                //TODO:FIXME:need to configure generator to print java.sql.Date/Times properly
+                jsonGen = resultSet.metadata.statement.connection.protocol.driverContext.genericObjectWriter
+                        .createGenerator(jsonGenBuffer);
+            } catch (IOException e) {
+                throw getErrorReporter().errorInResultHandling(e);
+            }
+        }
+        try {
+            jsonGen.writeObject(value);
+            jsonGen.flush();
+            return jsonGenBuffer.getBuffer().toString();
+        } catch (IOException e) {
+            throw getErrorReporter().errorInResultHandling(e);
+        } finally {
+            jsonGenBuffer.getBuffer().setLength(0);
+        }
+    }
+
+    ObjectReader createComplexColumnObjectReader(ObjectReader templateReader) {
+        return templateReader.withAttribute(ROW_STORE_ATTR_NAME, this);
+    }
+
+    static void configureDeserialization(ObjectMapper objectMapper, SimpleModule serdeModule) {
+        objectMapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true);
+        serdeModule.setDeserializerModifier(createADMFormatDeserializerModifier());
+    }
+
+    private static BeanDeserializerModifier createADMFormatDeserializerModifier() {
+        return new BeanDeserializerModifier() {
+            @Override
+            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
+                    JsonDeserializer<?> deserializer) {
+                if (String.class.equals(beanDesc.getClassInfo().getAnnotated())) {
+                    ADBRowStore rowStore = (ADBRowStore) config.getAttributes().getAttribute(ROW_STORE_ATTR_NAME);
+                    return rowStore.createADMFormatStringDeserializer();
+                } else {
+                    return deserializer;
+                }
+            }
+        };
+    }
+
+    private JsonDeserializer<?> createADMFormatStringDeserializer() {
+        return new JsonDeserializer<Object>() {
+            @Override
+            public Object deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
+                if (!parser.hasToken(JsonToken.VALUE_STRING)) {
+                    throw new IOException("Unexpected token");
+                }
+                try {
+                    ADBRowStore.this.reset();
+                    ADBRowStore.this.putColumn(0, parser.getTextCharacters(), parser.getTextOffset(),
+                            parser.getTextLength());
+                    return ADBRowStore.this.getObject(0);
+                } catch (SQLException e) {
+                    throw new IOException(e);
+                }
+            }
+        };
+    }
+
+    @FunctionalInterface
+    public interface ICharAccessor<T> {
+        char charAt(T input, int index);
+    }
+
+    private long parseInt64(CharSequence buffer) throws SQLException {
+        return parseInt64(buffer, 0, buffer.length(), CharSequence::charAt);
+    }
+
+    private long parseInt64(char[] buffer, int begin, int end) throws SQLException {
+        return parseInt64(buffer, begin, end, (input, index) -> input[index]);
+    }
+
+    private <T> long parseInt64(T buffer, int begin, int end, ICharAccessor<T> charAccessor) throws SQLException {
+        if (end < begin) {
+            throw new IllegalArgumentException();
+        }
+        boolean positive = true;
+        long value = 0;
+        int offset = begin;
+        char c = charAccessor.charAt(buffer, offset);
+        if (c == '+') {
+            offset++;
+        } else if (c == '-') {
+            offset++;
+            positive = false;
+        }
+        try {
+            for (; offset < end; offset++) {
+                c = charAccessor.charAt(buffer, offset);
+                if (c >= '0' && c <= '9') {
+                    value = Math.addExact(Math.multiplyExact(value, 10L), '0' - c);
+                } else {
+                    throw getErrorReporter().errorInProtocol(String.valueOf(c));
+                }
+            }
+            if (positive) {
+                value = Math.multiplyExact(value, -1L);
+            }
+            return value;
+        } catch (ArithmeticException e) {
+            throw getErrorReporter().errorInProtocol();
+        }
+    }
+
+    private byte parseTypeTag(char[] textChars, int textOffset, int textLength) throws SQLException {
+        if (textLength == 0) {
+            // empty string
+            parsedLength = 0;
+            return ADBDatatype.STRING.getTypeTag();
+        }
+        if (textChars[textOffset] == ADBProtocol.TEXT_DELIMITER) {
+            // any string
+            parsedLength = 1;
+            return ADBDatatype.STRING.getTypeTag();
+        }
+        // any type
+        int typeTagLength = 2;
+        if (textLength < typeTagLength) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        byte parsedTypeTag = getByteFromValidHexChars(textChars[textOffset], textChars[textOffset + 1]);
+        if (parsedTypeTag == ADBDatatype.MISSING.getTypeTag() || parsedTypeTag == ADBDatatype.NULL.getTypeTag()) {
+            parsedLength = typeTagLength;
+            return parsedTypeTag;
+        }
+        int delimiterLength = 1;
+        if (textLength < typeTagLength + delimiterLength) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        if (textChars[textOffset + typeTagLength] != ADBProtocol.TEXT_DELIMITER) {
+            throw getErrorReporter().errorInProtocol();
+        }
+        parsedLength = typeTagLength + delimiterLength;
+        return parsedTypeTag;
+    }
+
+    private byte getByteFromValidHexChars(char c0, char c1) throws SQLException {
+        return (byte) ((getValueFromValidHexChar(c0) << 4) + getValueFromValidHexChar(c1));
+    }
+
+    private int getValueFromValidHexChar(char c) throws SQLException {
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        }
+        if (c >= 'a' && c <= 'f') {
+            return 10 + c - 'a';
+        }
+        if (c >= 'A' && c <= 'F') {
+            return 10 + c - 'A';
+        }
+        throw getErrorReporter().errorInProtocol(String.valueOf(c));
+    }
+
+    private static int indexOf(char c, char[] array, int begin, int end) {
+        for (int i = begin; i < end; i++) {
+            if (array[i] == c) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private ADBErrorReporter getErrorReporter() {
+        return resultSet.getErrorReporter();
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBStatement.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBStatement.java
new file mode 100644
index 0000000..94ac5a3
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBStatement.java
@@ -0,0 +1,846 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+
+class ADBStatement extends ADBWrapperSupport implements java.sql.Statement {
+
+    static final List<Class<?>> SET_OBJECT_NON_ATOMIC = Arrays.asList(Object[].class, Collection.class, Map.class);
+
+    static final Map<Class<?>, AbstractValueSerializer> SERIALIZER_MAP = createSerializerMap();
+
+    protected final ADBConnection connection;
+    protected final String catalog;
+    protected final String schema;
+
+    protected final AtomicBoolean closed = new AtomicBoolean(false);
+    protected volatile boolean closeOnCompletion;
+
+    protected int queryTimeoutSeconds;
+    protected long maxRows;
+
+    // common result fields
+    protected int updateCount = -1;
+    protected List<ADBProtocol.QueryServiceResponse.Message> warnings;
+
+    // executeQuery() result fields
+    protected final ConcurrentLinkedQueue<ADBResultSet> resultSetsWithResources;
+    protected final ConcurrentLinkedQueue<WeakReference<ADBResultSet>> resultSetsWithoutResources;
+
+    // execute() result field
+    protected ADBProtocol.QueryServiceResponse executeResponse;
+    protected ADBResultSet executeResultSet;
+
+    // Lifecycle
+
+    ADBStatement(ADBConnection connection, String catalog, String schema) {
+        this.connection = Objects.requireNonNull(connection);
+        this.catalog = catalog;
+        this.schema = schema;
+        this.resultSetsWithResources = new ConcurrentLinkedQueue<>();
+        this.resultSetsWithoutResources = new ConcurrentLinkedQueue<>();
+    }
+
+    @Override
+    public void close() throws SQLException {
+        closeImpl(true, true);
+    }
+
+    void closeImpl(boolean closeResultSets, boolean notifyConnection) throws SQLException {
+        boolean wasClosed = closed.getAndSet(true);
+        if (wasClosed) {
+            return;
+        }
+        try {
+            if (closeResultSets) {
+                closeRegisteredResultSets();
+            }
+        } finally {
+            if (notifyConnection) {
+                connection.deregisterStatement(this);
+            }
+        }
+    }
+
+    @Override
+    public void closeOnCompletion() throws SQLException {
+        checkClosed();
+        closeOnCompletion = true;
+    }
+
+    @Override
+    public boolean isCloseOnCompletion() throws SQLException {
+        checkClosed();
+        return closeOnCompletion;
+    }
+
+    @Override
+    public boolean isClosed() {
+        return closed.get();
+    }
+
+    protected void checkClosed() throws SQLException {
+        if (isClosed()) {
+            throw getErrorReporter().errorObjectClosed(Statement.class);
+        }
+    }
+
+    // Execution
+
+    @Override
+    public ADBResultSet executeQuery(String sql) throws SQLException {
+        checkClosed();
+        return executeQueryImpl(sql, null);
+    }
+
+    protected ADBResultSet executeQueryImpl(String sql, List<?> args) throws SQLException {
+        // note: we're not assigning executeResponse field at this method
+        ADBProtocol.QueryServiceResponse response =
+                connection.protocol.submitStatement(sql, args, true, false, queryTimeoutSeconds, catalog, schema);
+        boolean isQuery = connection.protocol.isStatementCategory(response,
+                ADBProtocol.QueryServiceResponse.StatementCategory.QUERY);
+        if (!isQuery) {
+            throw getErrorReporter().errorInvalidStatementCategory();
+        }
+        warnings = connection.protocol.getWarningIfExists(response);
+        updateCount = -1;
+        return fetchResultSet(response);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql) throws SQLException {
+        checkClosed();
+        return executeUpdateImpl(sql, null);
+    }
+
+    @Override
+    public int executeUpdate(String sql) throws SQLException {
+        checkClosed();
+        return executeUpdateImpl(sql, null);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeUpdate");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeUpdate");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeLargeUpdate");
+    }
+
+    @Override
+    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeUpdate");
+    }
+
+    protected int executeUpdateImpl(String sql, List<Object> args) throws SQLException {
+        ADBProtocol.QueryServiceResponse response =
+                connection.protocol.submitStatement(sql, args, false, false, queryTimeoutSeconds, catalog, schema);
+        boolean isQuery = connection.protocol.isStatementCategory(response,
+                ADBProtocol.QueryServiceResponse.StatementCategory.QUERY);
+        // TODO: remove result set on the server (both query and update returning cases)
+        if (isQuery) {
+            throw getErrorReporter().errorInvalidStatementCategory();
+        }
+        warnings = connection.protocol.getWarningIfExists(response);
+        updateCount = connection.protocol.getUpdateCount(response);
+        return updateCount;
+    }
+
+    @Override
+    public boolean execute(String sql) throws SQLException {
+        checkClosed();
+        return executeImpl(sql, null);
+    }
+
+    @Override
+    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "execute");
+    }
+
+    @Override
+    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "execute");
+    }
+
+    @Override
+    public boolean execute(String sql, String[] columnNames) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "execute");
+    }
+
+    protected boolean executeImpl(String sql, List<Object> args) throws SQLException {
+        ADBProtocol.QueryServiceResponse response =
+                connection.protocol.submitStatement(sql, args, false, false, queryTimeoutSeconds, catalog, schema);
+        warnings = connection.protocol.getWarningIfExists(response);
+        boolean isQuery = connection.protocol.isStatementCategory(response,
+                ADBProtocol.QueryServiceResponse.StatementCategory.QUERY);
+        if (isQuery) {
+            updateCount = -1;
+            executeResponse = response;
+            return true;
+        } else {
+            updateCount = connection.protocol.getUpdateCount(response);
+            executeResponse = null;
+            return false;
+        }
+    }
+
+    @Override
+    public void cancel() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "cancel");
+    }
+
+    @Override
+    public int getQueryTimeout() throws SQLException {
+        checkClosed();
+        return queryTimeoutSeconds;
+    }
+
+    @Override
+    public void setQueryTimeout(int timeoutSeconds) throws SQLException {
+        checkClosed();
+        if (timeoutSeconds < 0) {
+            throw getErrorReporter().errorParameterValueNotSupported("timeoutSeconds");
+        }
+        queryTimeoutSeconds = timeoutSeconds;
+    }
+
+    @Override
+    public void setEscapeProcessing(boolean enable) throws SQLException {
+        checkClosed();
+    }
+
+    // Batch execution
+
+    @Override
+    public long[] executeLargeBatch() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeLargeBatch");
+    }
+
+    @Override
+    public int[] executeBatch() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "executeBatch");
+    }
+
+    @Override
+    public void addBatch(String sql) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "addBatch");
+    }
+
+    @Override
+    public void clearBatch() throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "clearBatch");
+    }
+
+    // Result access
+
+    @Override
+    public ADBResultSet getResultSet() throws SQLException {
+        checkClosed();
+        ADBProtocol.QueryServiceResponse response = executeResponse;
+        if (response == null) {
+            return null;
+        }
+        ADBResultSet rs = fetchResultSet(response);
+        executeResultSet = rs;
+        executeResponse = null;
+        return rs;
+    }
+
+    @Override
+    public boolean getMoreResults() throws SQLException {
+        return getMoreResults(Statement.CLOSE_ALL_RESULTS);
+    }
+
+    @Override
+    public boolean getMoreResults(int current) throws SQLException {
+        checkClosed();
+        ADBResultSet rs = executeResultSet;
+        executeResultSet = null;
+        if (rs != null && current != Statement.KEEP_CURRENT_RESULT) {
+            rs.closeImpl(true);
+        }
+        return false;
+    }
+
+    @Override
+    public int getResultSetType() throws SQLException {
+        checkClosed();
+        return ResultSet.TYPE_FORWARD_ONLY;
+    }
+
+    @Override
+    public int getResultSetConcurrency() throws SQLException {
+        checkClosed();
+        return ResultSet.CONCUR_READ_ONLY;
+    }
+
+    @Override
+    public int getResultSetHoldability() throws SQLException {
+        checkClosed();
+        return ADBResultSet.RESULT_SET_HOLDABILITY;
+    }
+
+    @Override
+    public ResultSet getGeneratedKeys() throws SQLException {
+        checkClosed();
+        return createEmptyResultSet();
+    }
+
+    @Override
+    public long getLargeUpdateCount() throws SQLException {
+        checkClosed();
+        return updateCount;
+    }
+
+    @Override
+    public int getUpdateCount() throws SQLException {
+        return (int) getLargeUpdateCount();
+    }
+
+    // ResultSet lifecycle
+
+    private ADBResultSet fetchResultSet(ADBProtocol.QueryServiceResponse execResponse) throws SQLException {
+        List<ADBColumn> columns = connection.protocol.getColumns(execResponse);
+        if (getLogger().isLoggable(Level.FINER)) {
+            getLogger().log(Level.FINE, "result schema " + columns);
+        }
+        if (connection.protocol.isExplainOnly(execResponse)) {
+            AbstractValueSerializer stringSer = getADMFormatSerializer(String.class);
+            ArrayNode explainResult = connection.protocol.fetchExplainOnlyResult(execResponse, stringSer);
+            return createSystemResultSet(columns, explainResult);
+        } else {
+            JsonParser rowParser = connection.protocol.fetchResult(execResponse);
+            return createResultSetImpl(columns, rowParser, true, maxRows);
+        }
+    }
+
+    protected ADBResultSet createSystemResultSet(List<ADBColumn> columns, ArrayNode values) {
+        JsonParser rowParser = connection.protocol.createJsonParser(values);
+        return createResultSetImpl(columns, rowParser, false, 0);
+    }
+
+    protected ADBResultSet createEmptyResultSet() {
+        ArrayNode empty = (ArrayNode) connection.protocol.driverContext.genericObjectReader.createArrayNode();
+        return createSystemResultSet(Collections.emptyList(), empty);
+    }
+
+    private ADBResultSet createResultSetImpl(List<ADBColumn> columns, JsonParser rowParser,
+            boolean rowParserOwnsResources, long maxRows) {
+        ADBResultSetMetaData metadata = new ADBResultSetMetaData(this, columns);
+        ADBResultSet rs = new ADBResultSet(metadata, rowParser, rowParserOwnsResources, maxRows);
+        registerResultSet(rs);
+        return rs;
+    }
+
+    private void registerResultSet(ADBResultSet rs) {
+        if (rs.rowParserOwnsResources) {
+            resultSetsWithResources.add(rs);
+        } else {
+            resultSetsWithoutResources.removeIf(ADBStatement::isEmptyReference);
+            resultSetsWithoutResources.add(new WeakReference<>(rs));
+        }
+    }
+
+    protected void deregisterResultSet(ADBResultSet rs) {
+        if (rs.rowParserOwnsResources) {
+            resultSetsWithResources.remove(rs);
+        } else {
+            resultSetsWithoutResources.removeIf(ref -> {
+                ADBResultSet refrs = ref.get();
+                return refrs == null || refrs == rs;
+            });
+        }
+        if (closeOnCompletion && resultSetsWithResources.isEmpty() && resultSetsWithoutResources.isEmpty()) {
+            try {
+                closeImpl(false, true);
+            } catch (SQLException e) {
+                // this exception shouldn't happen because there are no result sets to close
+                if (getLogger().isLoggable(Level.FINE)) {
+                    getLogger().log(Level.FINE, e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    private void closeRegisteredResultSets() throws SQLException {
+        SQLException err = null;
+        try {
+            closedRegisteredResultSetsImpl(resultSetsWithResources, Function.identity());
+        } catch (SQLException e) {
+            err = e;
+        }
+        try {
+            closedRegisteredResultSetsImpl(resultSetsWithoutResources, Reference::get);
+        } catch (SQLException e) {
+            if (err != null) {
+                e.addSuppressed(err);
+            }
+            err = e;
+        }
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    private <T> void closedRegisteredResultSetsImpl(Queue<T> queue, Function<T, ADBResultSet> rsAccessor)
+            throws SQLException {
+        SQLException err = null;
+        T item;
+        while ((item = queue.poll()) != null) {
+            ADBResultSet rs = rsAccessor.apply(item);
+            if (rs != null) {
+                try {
+                    rs.closeImpl(false);
+                } catch (SQLException e) {
+                    if (err != null) {
+                        e.addSuppressed(err);
+                    }
+                    err = e;
+                }
+            }
+        }
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    private static boolean isEmptyReference(Reference<ADBResultSet> ref) {
+        return ref.get() == null;
+    }
+
+    // Result control
+
+    @Override
+    public void setLargeMaxRows(long maxRows) throws SQLException {
+        checkClosed();
+        if (maxRows < 0) {
+            throw getErrorReporter().errorParameterValueNotSupported("maxRows");
+        }
+        this.maxRows = maxRows;
+    }
+
+    @Override
+    public void setMaxRows(int maxRows) throws SQLException {
+        setLargeMaxRows(maxRows);
+    }
+
+    @Override
+    public long getLargeMaxRows() throws SQLException {
+        checkClosed();
+        return maxRows;
+    }
+
+    @Override
+    public int getMaxRows() throws SQLException {
+        return (int) getLargeMaxRows();
+    }
+
+    @Override
+    public void setCursorName(String name) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "setCursorName");
+    }
+
+    // Unsupported hints (ignored)
+
+    @Override
+    public int getFetchDirection() throws SQLException {
+        checkClosed();
+        return ResultSet.FETCH_FORWARD;
+    }
+
+    @Override
+    public void setFetchDirection(int direction) throws SQLException {
+        checkClosed();
+        switch (direction) {
+            case ResultSet.FETCH_FORWARD:
+            case ResultSet.FETCH_REVERSE:
+            case ResultSet.FETCH_UNKNOWN:
+                // ignore this hint
+                break;
+            default:
+                throw getErrorReporter().errorParameterValueNotSupported("direction");
+        }
+    }
+
+    @Override
+    public int getFetchSize() throws SQLException {
+        checkClosed();
+        return 1;
+    }
+
+    @Override
+    public void setFetchSize(int rows) throws SQLException {
+        checkClosed();
+        if (rows < 0) {
+            throw getErrorReporter().errorParameterNotSupported("rows");
+        }
+    }
+
+    @Override
+    public int getMaxFieldSize() throws SQLException {
+        checkClosed();
+        return 0;
+    }
+
+    @Override
+    public void setMaxFieldSize(int maxFieldSize) throws SQLException {
+        throw getErrorReporter().errorMethodNotSupported(Statement.class, "setMaxFieldSize");
+    }
+
+    @Override
+    public boolean isPoolable() throws SQLException {
+        checkClosed();
+        return false;
+    }
+
+    @Override
+    public void setPoolable(boolean poolable) throws SQLException {
+        checkClosed();
+    }
+
+    // Errors and warnings
+
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        checkClosed();
+        return warnings != null ? connection.protocol.createSQLWarning(warnings) : null;
+    }
+
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkClosed();
+        warnings = null;
+    }
+
+    @Override
+    protected ADBErrorReporter getErrorReporter() {
+        return connection.getErrorReporter();
+    }
+
+    protected Logger getLogger() {
+        return connection.getLogger();
+    }
+
+    // Ownership
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        checkClosed();
+        return connection;
+    }
+
+    ADBStatement getResultSetStatement(ADBResultSet rs) {
+        return rs.metadata.statement;
+    }
+
+    // Serialization
+
+    static void configureSerialization(SimpleModule serdeModule) {
+        serdeModule.setSerializerModifier(createADMFormatSerializerModifier());
+    }
+
+    static AbstractValueSerializer getADMFormatSerializer(Class<?> cls) {
+        return SERIALIZER_MAP.get(cls);
+    }
+
+    private static BeanSerializerModifier createADMFormatSerializerModifier() {
+        return new BeanSerializerModifier() {
+            @Override
+            public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
+                    JsonSerializer<?> serializer) {
+                Class<?> cls = beanDesc.getClassInfo().getAnnotated();
+                if (isSetObjectCompatible(cls)) {
+                    AbstractValueSerializer ser = getADMFormatSerializer(cls);
+                    return ser != null ? ser : super.modifySerializer(config, beanDesc, serializer);
+                } else {
+                    return null;
+                }
+            }
+        };
+    }
+
+    static boolean isSetObjectCompatible(Class<?> cls) {
+        if (ADBRowStore.OBJECT_ACCESSORS_ATOMIC.containsKey(cls)) {
+            return true;
+        }
+        for (Class<?> aClass : SET_OBJECT_NON_ATOMIC) {
+            if (aClass.isAssignableFrom(cls)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Map<Class<?>, AbstractValueSerializer> createSerializerMap() {
+        Map<Class<?>, AbstractValueSerializer> serializerMap = new HashMap<>();
+        registerSerializer(serializerMap, createGenericSerializer(Byte.class, ADBDatatype.TINYINT));
+        registerSerializer(serializerMap, createGenericSerializer(Short.class, ADBDatatype.SMALLINT));
+        registerSerializer(serializerMap, createGenericSerializer(Integer.class, ADBDatatype.INTEGER));
+        registerSerializer(serializerMap, createGenericSerializer(UUID.class, ADBDatatype.UUID));
+        // Long is serialized as JSON number by Jackson
+        registerSerializer(serializerMap, createFloatSerializer());
+        registerSerializer(serializerMap, createDoubleSerializer());
+        registerSerializer(serializerMap, createStringSerializer());
+        registerSerializer(serializerMap, createSqlDateSerializer());
+        registerSerializer(serializerMap, createLocalDateSerializer());
+        registerSerializer(serializerMap, createSqlTimeSerializer());
+        registerSerializer(serializerMap, createLocalTimeSerializer());
+        registerSerializer(serializerMap, createSqlTimestampSerializer());
+        registerSerializer(serializerMap, createInstantSerializer());
+        registerSerializer(serializerMap, createPeriodSerializer());
+        registerSerializer(serializerMap, createDurationSerializer());
+        return serializerMap;
+    }
+
+    private static void registerSerializer(Map<Class<?>, AbstractValueSerializer> map,
+            AbstractValueSerializer serializer) {
+        map.put(serializer.getJavaType(), serializer);
+    }
+
+    private static ATaggedValueSerializer createGenericSerializer(Class<?> javaType, ADBDatatype ADBDatatype) {
+        return new ATaggedValueSerializer(javaType, ADBDatatype) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                out.append(value);
+            }
+        };
+    }
+
+    private static AbstractValueSerializer createStringSerializer() {
+        return new AbstractValueSerializer(java.lang.String.class) {
+            @Override
+            public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+                gen.writeString(serializeToString(value));
+            }
+
+            @Override
+            protected String serializeToString(Object value) {
+                return ADBProtocol.TEXT_DELIMITER + String.valueOf(value);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createFloatSerializer() {
+        return new ATaggedValueSerializer(Float.class, ADBDatatype.FLOAT) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                int bits = Float.floatToIntBits((Float) value);
+                out.append((long) bits);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createDoubleSerializer() {
+        return new ATaggedValueSerializer(Double.class, ADBDatatype.DOUBLE) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long bits = Double.doubleToLongBits((Double) value);
+                out.append(bits);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createSqlDateSerializer() {
+        return new ATaggedValueSerializer(java.sql.Date.class, ADBDatatype.DATE) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = ((Date) value).getTime();
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createLocalDateSerializer() {
+        return new ATaggedValueSerializer(java.time.LocalDate.class, ADBDatatype.DATE) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = TimeUnit.DAYS.toMillis(((LocalDate) value).toEpochDay());
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createSqlTimeSerializer() {
+        return new ATaggedValueSerializer(java.sql.Time.class, ADBDatatype.TIME) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = ((Time) value).getTime();
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createLocalTimeSerializer() {
+        return new ATaggedValueSerializer(java.time.LocalTime.class, ADBDatatype.TIME) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = TimeUnit.NANOSECONDS.toMillis(((LocalTime) value).toNanoOfDay());
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createSqlTimestampSerializer() {
+        return new ATaggedValueSerializer(java.sql.Timestamp.class, ADBDatatype.DATETIME) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = ((Timestamp) value).getTime();
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createInstantSerializer() {
+        return new ATaggedValueSerializer(java.time.Instant.class, ADBDatatype.DATETIME) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = ((Instant) value).toEpochMilli();
+                out.append(millis);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createPeriodSerializer() {
+        return new ATaggedValueSerializer(java.time.Period.class, ADBDatatype.YEARMONTHDURATION) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long months = ((Period) value).toTotalMonths();
+                out.append(months);
+            }
+        };
+    }
+
+    private static ATaggedValueSerializer createDurationSerializer() {
+        return new ATaggedValueSerializer(java.time.Duration.class, ADBDatatype.DAYTIMEDURATION) {
+            @Override
+            protected void serializeNonTaggedValue(Object value, StringBuilder out) {
+                long millis = ((Duration) value).toMillis();
+                out.append(millis);
+            }
+        };
+    }
+
+    static abstract class AbstractValueSerializer extends JsonSerializer<Object> {
+
+        protected final Class<?> javaType;
+
+        protected AbstractValueSerializer(Class<?> javaType) {
+            this.javaType = Objects.requireNonNull(javaType);
+        }
+
+        protected Class<?> getJavaType() {
+            return javaType;
+        }
+
+        abstract String serializeToString(Object value);
+    }
+
+    private static abstract class ATaggedValueSerializer extends AbstractValueSerializer {
+
+        protected final ADBDatatype adbType;
+
+        protected ATaggedValueSerializer(Class<?> javaType, ADBDatatype adbType) {
+            super(javaType);
+            this.adbType = Objects.requireNonNull(adbType);
+        }
+
+        @Override
+        public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+            gen.writeString(serializeToString(value)); // TODO:optimize?
+        }
+
+        protected final String serializeToString(Object value) {
+            StringBuilder textBuilder = new StringBuilder(64); // TODO:optimize?
+            printByteAsHex(adbType.getTypeTag(), textBuilder);
+            textBuilder.append(ADBProtocol.TEXT_DELIMITER);
+            serializeNonTaggedValue(value, textBuilder);
+            return textBuilder.toString();
+        }
+
+        protected abstract void serializeNonTaggedValue(Object value, StringBuilder out);
+
+        private static void printByteAsHex(byte b, StringBuilder out) {
+            out.append(hex((b >>> 4) & 0x0f));
+            out.append(hex(b & 0x0f));
+        }
+
+        private static char hex(int i) {
+            return (char) (i + (i < 10 ? '0' : ('A' - 10)));
+        }
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBWrapperSupport.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBWrapperSupport.java
new file mode 100644
index 0000000..30cbfd0
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBWrapperSupport.java
@@ -0,0 +1,41 @@
+/*
+ * 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.asterix.jdbc.core;
+
+import java.sql.SQLException;
+import java.sql.Wrapper;
+
+abstract class ADBWrapperSupport implements Wrapper {
+
+    @Override
+    public final boolean isWrapperFor(Class<?> iface) {
+        return iface.isInstance(this);
+    }
+
+    @Override
+    public final <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!iface.isInstance(this)) {
+            throw getErrorReporter().errorUnwrapTypeMismatch(iface);
+        }
+        return iface.cast(this);
+    }
+
+    protected abstract ADBErrorReporter getErrorReporter();
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-driver/pom.xml b/asterixdb-jdbc/asterix-jdbc-driver/pom.xml
new file mode 100644
index 0000000..afac089
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-driver/pom.xml
@@ -0,0 +1,137 @@
+<!--
+ ! 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.
+ !-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>apache-asterixdb</artifactId>
+    <groupId>org.apache.asterix</groupId>
+    <version>0.9.7-SNAPSHOT</version>
+  </parent>
+  <artifactId>asterix-jdbc-driver</artifactId>
+
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+      <comments>A business-friendly OSS license</comments>
+    </license>
+  </licenses>
+
+  <properties>
+    <root.dir>${basedir}/..</root.dir>
+    <source.jdk.version>1.8</source.jdk.version>
+    <target.jdk.version>1.8</target.jdk.version>
+    <source.java.package.path>org/apache/asterix/jdbc/</source.java.package.path>
+    <implementation.title>Apache AsterixDB JDBC Driver</implementation.title>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.asterix</groupId>
+      <artifactId>asterix-jdbc-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestSections>
+              <manifestSection>
+                <name>${source.java.package.path}</name>
+                <manifestEntries>
+                  <Implementation-Title>${implementation.title}</Implementation-Title>
+                  <Implementation-Version>${project.version}</Implementation-Version>
+                  <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+                </manifestEntries>
+              </manifestSection>
+            </manifestSections>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <shadedArtifactAttached>true</shadedArtifactAttached>
+              <shadedClassifierName>dist</shadedClassifierName>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"/>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
+                  <addHeader>false</addHeader>
+                </transformer>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.PluginXmlResourceTransformer"/>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>org.apache.asterix:asterix-jdbc-core</artifact>
+                  <excludes>
+                    <exclude>META-INF/MANIFEST.MF</exclude>
+                  </excludes>
+                </filter>
+                <filter>
+                  <artifact>org.apache.httpcomponents:*</artifact>
+                  <excludes>
+                    <exclude>module-info.class</exclude>
+                    <exclude>META-INF/DEPENDENCIES</exclude>
+                    <exclude>META-INF/MANIFEST.MF</exclude>
+                  </excludes>
+                </filter>
+                <filter>
+                  <artifact>commons-logging:*</artifact>
+                  <excludes>
+                    <exclude>module-info.class</exclude>
+                    <exclude>META-INF/MANIFEST.MF</exclude>
+                  </excludes>
+                </filter>
+                <filter>
+                  <artifact>commons-codec:*</artifact>
+                  <excludes>
+                    <exclude>module-info.class</exclude>
+                    <exclude>META-INF/MANIFEST.MF</exclude>
+                  </excludes>
+                </filter>
+                <filter>
+                  <artifact>com.fasterxml.jackson.core:*</artifact>
+                  <excludes>
+                    <exclude>module-info.class</exclude>
+                    <exclude>META-INF/MANIFEST.MF</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java
new file mode 100644
index 0000000..4d175ed
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.asterix.jdbc;
+
+import org.apache.asterix.jdbc.core.ADBDriverBase;
+
+public final class Driver extends ADBDriverBase implements java.sql.Driver {
+
+    private static final String DRIVER_SCHEME = "asterixdb:";
+
+    private static final int DEFAULT_API_PORT = 19002;
+
+    static {
+        setupLogging(Driver.class);
+        registerDriver(new Driver());
+    }
+
+    public Driver() {
+        super(DRIVER_SCHEME, DEFAULT_API_PORT);
+    }
+}
diff --git a/asterixdb-jdbc/asterix-jdbc-driver/src/main/resources/META-INF/services/java.sql.Driver b/asterixdb-jdbc/asterix-jdbc-driver/src/main/resources/META-INF/services/java.sql.Driver
new file mode 100644
index 0000000..626a039
--- /dev/null
+++ b/asterixdb-jdbc/asterix-jdbc-driver/src/main/resources/META-INF/services/java.sql.Driver
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+org.apache.asterix.jdbc.Driver
\ No newline at end of file