changeset 64:bb0d66ad7dc6 embedded

More done
author Pedro Ferreira <pedro.ferreira@monetdbsolutions.com>
date Thu, 01 Dec 2016 16:52:27 +0100 (2016-12-01)
parents 6325594f01af
children e605cdd6373f
files src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java src/main/java/nl/cwi/monetdb/mcl/MCLException.java src/main/java/nl/cwi/monetdb/mcl/connection/AbstractMonetDBConnection.java src/main/java/nl/cwi/monetdb/mcl/connection/Debugger.java src/main/java/nl/cwi/monetdb/mcl/connection/DeleteMe.java src/main/java/nl/cwi/monetdb/mcl/connection/EmbeddedMonetDB.java src/main/java/nl/cwi/monetdb/mcl/connection/MCLException.java src/main/java/nl/cwi/monetdb/mcl/connection/MapiConnection.java src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBLanguage.java src/main/java/nl/cwi/monetdb/mcl/io/SocketConnection.java src/main/java/nl/cwi/monetdb/mcl/io/SocketIOHandler.java src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocolParser.java src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java src/main/java/nl/cwi/monetdb/merovingian/Control.java src/main/java/nl/cwi/monetdb/responses/AutoCommitResponse.java src/main/java/nl/cwi/monetdb/responses/DataBlockResponse.java src/main/java/nl/cwi/monetdb/responses/IResponse.java src/main/java/nl/cwi/monetdb/responses/ResponseList.java src/main/java/nl/cwi/monetdb/responses/ResultSetResponse.java src/main/java/nl/cwi/monetdb/responses/SchemaResponse.java src/main/java/nl/cwi/monetdb/responses/SendThread.java src/main/java/nl/cwi/monetdb/responses/UpdateResponse.java src/main/java/nl/cwi/monetdb/util/ChannelSecurity.java src/main/java/nl/cwi/monetdb/util/SQLRestore.java
diffstat 27 files changed, 3046 insertions(+), 3169 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java
@@ -1,2698 +1,1287 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0.  If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
- */
-
 package nl.cwi.monetdb.jdbc;
 
-import java.io.File;
-import java.io.IOException;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-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.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.sql.SQLWarning;
-import java.sql.SQLXML;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.sql.Struct;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.WeakHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
 import nl.cwi.monetdb.jdbc.types.INET;
 import nl.cwi.monetdb.jdbc.types.URL;
-import nl.cwi.monetdb.mcl.MCLException;
-import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
-import nl.cwi.monetdb.mcl.io.AbstractMCLWriter;
-import nl.cwi.monetdb.mcl.connection.EmbeddedMonetDB;
-import nl.cwi.monetdb.mcl.connection.DeleteMe;
-import nl.cwi.monetdb.mcl.connection.AbstractMonetDBConnection;
-import nl.cwi.monetdb.mcl.parser.HeaderLineParser;
-import nl.cwi.monetdb.mcl.parser.StartOfHeaderParser;
+import nl.cwi.monetdb.mcl.connection.MCLException;
+import nl.cwi.monetdb.mcl.connection.Debugger;
+import nl.cwi.monetdb.mcl.connection.MonetDBLanguage;
 import nl.cwi.monetdb.mcl.parser.MCLParseException;
+import nl.cwi.monetdb.responses.ResponseList;
+import nl.cwi.monetdb.responses.SendThread;
+
+import java.io.*;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.sql.*;
+import java.util.*;
+import java.util.concurrent.Executor;
 
 /**
  * A {@link Connection} suitable for the MonetDB database.
- * 
+ *
  * This connection represents a connection (session) to a MonetDB
  * database. SQL statements are executed and results are returned within
  * the context of a connection. This Connection object holds a physical
  * connection to the MonetDB database.
- * 
+ *
  * A Connection object's database should able to provide information
  * describing its tables, its supported SQL grammar, its stored
  * procedures, the capabilities of this connection, and so on. This
  * information is obtained with the getMetaData method.
- * 
+ *
  * Note: By default a Connection object is in auto-commit mode, which
  * means that it automatically commits changes after executing each
  * statement. If auto-commit mode has been disabled, the method commit
  * must be called explicitly in order to commit changes; otherwise,
  * database changes will not be saved.
- * 
+ *
  * The current state of this connection is that it nearly implements the
  * whole Connection interface.
  *
  * @author Fabian Groffen
  * @version 1.2
  */
-public class MonetConnection extends MonetWrapper implements Connection {
-
-	/** A connection to mserver5 either through MAPI with TCP or embedded */
-	private final AbstractMonetDBConnection server;
-	/** The Reader from the server */
-	private final AbstractMCLReader in;
-	/** The Writer to the server */
-	private final AbstractMCLWriter out;
-	/** A StartOfHeaderParser declared for reuse. */
-	private final StartOfHeaderParser sohp;
-
-	/** Whether this Connection is closed (and cannot be used anymore) */
-	private boolean closed;
+public abstract class MonetConnection extends MonetWrapper implements Connection {
 
-	/** Whether this Connection is in autocommit mode */
-	private boolean autoCommit = true;
-
-	/** The stack of warnings for this Connection object */
-	private SQLWarning warnings = null;
-	/** The Connection specific mapping of user defined types to Java
-	 * types */
-	private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() {/**
-		 * 
-		 */
-		private static final long serialVersionUID = 1L;
-		{
-			put("inet", INET.class);
-			put("url",  URL.class);
-		}
-	};
+    /** the successful processed input properties */
+    private final Properties conn_props;
 
-	// See javadoc for documentation about WeakHashMap if you don't know what
-	// it does !!!NOW!!! (only when you deal with it of course)
-	/** A Map containing all (active) Statements created from this Connection */
-	private Map<Statement,?> statements = new WeakHashMap<Statement, Object>();
-
-	/** The number of results we receive from the server at once */
-	private int curReplySize = -1;	// the server by default uses -1 (all)
-
-	/** Whether or not BLOB is mapped to BINARY within the driver */
-	private final boolean blobIsBinary;
+    /** The language to connect with */
+    protected MonetDBLanguage currentMonetDBLanguage = MonetDBLanguage.LANG_SQL;
+    /** The database to connect to */
+    protected String database;
+    /** Authentication hash method */
+    protected final String hash;
+    /** An optional thread that is used for sending large queries */
+    private SendThread sendThread;
+    /** Whether this Connection is closed (and cannot be used anymore) */
+    private boolean closed;
+    /** Whether this Connection is in autocommit mode */
+    private boolean autoCommit = true;
+    /** The stack of warnings for this Connection object */
+    private SQLWarning warnings;
+    /** The Connection specific mapping of user defined types to Java types */
+    private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() {
+        private static final long serialVersionUID = 1L; {
+            put("inet", INET.class);
+            put("url",  URL.class);
+        }
+    };
 
-	/**
-	 * Constructor of a Connection for MonetDB. At this moment the
-	 * current implementation limits itself to storing the given host,
-	 * database, username and password for later use by the
-	 * createStatement() call.  This constructor is only accessible to
-	 * classes from the jdbc package.
-	 *
-	 * @param props a Property hashtable holding the properties needed for
-	 *              connecting
-	 * @throws SQLException if a database error occurs
-	 * @throws IllegalArgumentException is one of the arguments is null or empty
-	 */
-	MonetConnection(Properties props)
-		throws SQLException, IllegalArgumentException
-	{
-		String database = props.getProperty("database");
-		if (database == null || database.trim().isEmpty())
-			throw new IllegalArgumentException("database should not be null or empty");
-		boolean isEmbedded = Boolean.parseBoolean(props.getProperty("embedded"));
-		String username = props.getProperty("user");
-		String password = props.getProperty("password");
-		boolean debug = Boolean.valueOf(props.getProperty("debug"));
-		blobIsBinary = Boolean.valueOf(props.getProperty("treat_blob_as_binary"));
+    // See javadoc for documentation about WeakHashMap if you don't know what
+    // it does !!!NOW!!! (only when you deal with it of course)
+    /** A Map containing all (active) Statements created from this Connection */
+    private Map<Statement,?> statements = new WeakHashMap<Statement, Object>();
 
-		if(isEmbedded) {
-			String directory = props.getProperty("directory");
-			if (directory == null || directory.trim().isEmpty())
-				throw new IllegalArgumentException("directory should not be null or empty");
+    /** The number of results we receive from the server at once */
+    private int curReplySize = -1; // the server by default uses -1 (all)
 
-			server = new EmbeddedMonetDB("localhost", -1, database, username, debug, "sql", null, directory);
-		} else {
-			String hostname = props.getProperty("host");
-			String hash = props.getProperty("hash");
-			String language = props.getProperty("language");
-			int port = 0;
-			int sockTimeout = 0;
+    /** Whether or not BLOB is mapped to BINARY within the driver */
+    private final boolean blobIsBinary;
 
-			try {
-				port = Integer.parseInt(props.getProperty("port"));
-			} catch (NumberFormatException e) {
-			}
-			try {
-				sockTimeout = Integer.parseInt(props.getProperty("so_timeout"));
-			} catch (NumberFormatException e) {
-			}
+    protected boolean isDebugging;
+
+    protected Debugger ourSavior;
 
-			// check input arguments
-			if (hostname == null || hostname.trim().isEmpty())
-				throw new IllegalArgumentException("hostname should not be null or empty");
-			if (port == 0)
-				throw new IllegalArgumentException("port should not be 0");
-			if (username == null || username.trim().isEmpty())
-				throw new IllegalArgumentException("user should not be null or empty");
-			if (password == null || password.trim().isEmpty())
-				throw new IllegalArgumentException("password should not be null or empty");
-			if (language == null || language.trim().isEmpty()) {
-				language = "sql";
-				addWarning("No language given, defaulting to 'sql'", "M1M05");
-			}
-			server = new DeleteMe(hostname, port, database, username, debug, language, hash);
-			try {
-				server.setSoTimeout(sockTimeout);
-			} catch (SocketException e) {
-				addWarning("The socket timeout could not be set", "M1M05");
-			}
-			server.setLanguage(language);
-		}
+    /**
+     * Constructor of a Connection for MonetDB. At this moment the
+     * current implementation limits itself to storing the given host,
+     * database, username and password for later use by the
+     * createStatement() call.  This constructor is only accessible to
+     * classes from the jdbc package.
+     *
+     * @throws IOException if an error occurs
+     */
+    public MonetConnection(Properties props, String database, String hash, String language, boolean blobIsBinary, boolean isDebugging) throws IOException {
+        this.conn_props = props;
+        this.database = database;
+        this.hash = hash;
+        this.currentMonetDBLanguage = MonetDBLanguage.GetLanguageFromString(language);
+        this.blobIsBinary = blobIsBinary;
+        this.isDebugging = isDebugging;
+    }
 
-		// we're debugging here... uhm, should be off in real life
-		if (debug) {
-			try {
-				String fname = props.getProperty("logfile", "monet_" +
-					System.currentTimeMillis() + ".log");
-				File f = new File(fname);
-				int ext = fname.lastIndexOf('.');
-				if (ext < 0) ext = fname.length();
-				String pre = fname.substring(0, ext);
-				String suf = fname.substring(ext);
-
-				for (int i = 1; f.exists(); i++) {
-					f = new File(pre + "-" + i + suf);
-				}
+    public MonetDBLanguage getCurrentMonetDBLanguage() {
+        return currentMonetDBLanguage;
+    }
 
-				server.debug(f.getAbsolutePath());
-			} catch (IOException ex) {
-				throw new SQLException("Opening logfile failed: " + ex.getMessage(), "08M01");
-			}
-		}
+    public void setCurrentMonetDBLanguage(MonetDBLanguage currentMonetDBLanguage) {
+        this.currentMonetDBLanguage = currentMonetDBLanguage;
+    }
 
-		try {
-			List<String> warnings = server.connect(username, password);
-			if(warnings != null) {
-				for (String warning : warnings) {
-					addWarning(warning, "01M02");
-				}
-			}
-
-			in = server.getReader();
-			out = server.getWriter();
-			String error = in.waitForPrompt();
-			if (error != null)
-				throw new SQLException(error.substring(6), "08001");
+    public void setDebugging(String filename) throws IOException {
+        ourSavior = new Debugger(filename);
+    }
 
-			sohp = server.getStartOfHeaderParser();
-		} catch (IOException e) {
-			throw new SQLException("Unable to connect (" + server.getHostname() + ":" + server.getPort() + "): " + e.getMessage(), "08006");
-		} catch (MCLParseException e) {
-			throw new SQLException(e.getMessage(), "08001");
-		} catch (MCLException e) {
-			String[] connex = e.getMessage().split("\n");
-			SQLException sqle = new SQLException(connex[0], "08001", e);
-			for (int i = 1; i < connex.length; i++) {
-				sqle.setNextException(new SQLException(connex[1], "08001"));
-			}
-			throw sqle;
-		}
-
-		// the following initialisers are only valid when the language
-		// is SQL...
-		if (server.getLang() == AbstractMonetDBConnection.LANG_SQL) {
-			// enable auto commit
-			setAutoCommit(true);
-			// set our time zone on the server
-			Calendar cal = Calendar.getInstance();
-			int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
-			offset /= (60 * 1000); // milliseconds to minutes
-			String tz = offset < 0 ? "-" : "+";
-			tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":";
-			offset -= (offset / 60) * 60;
-			tz += (offset < 10 ? "0" : "") + offset;
-			sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE");
-		}
-	}
-
-	protected AbstractMonetDBConnection getServer() {
-		return server;
-	}
-
-	//== methods of interface Connection
+    /**
+     * Connects to the given host and port, logging in as the given
+     * user.  If followRedirect is false, a RedirectionException is
+     * thrown when a redirect is encountered.
+     *
+     * @return A List with informational (warning) messages. If this
+     * 		list is empty; then there are no warnings.
+     * @throws IOException if an I/O error occurs when creating the
+     *         socket
+     * @throws MCLParseException if bogus data is received
+     * @throws MCLException if an MCL related error occurs
+     */
+    public abstract List<String> connect(String user, String pass) throws IOException, MCLParseException, MCLException;
 
-	/**
-	 * Clears all warnings reported for this Connection object. After a
-	 * call to this method, the method getWarnings returns null until a
-	 * new warning is reported for this Connection object.
-	 */
-	@Override
-	public void clearWarnings() {
-		warnings = null;
-	}
+    public abstract int getBlockSize();
+
+    public abstract int getSoTimeout() throws SocketException;
 
-	/**
-	 * Releases this Connection object's database and JDBC resources
-	 * immediately instead of waiting for them to be automatically
-	 * released. All Statements created from this Connection will be
-	 * closed when this method is called.
-	 * 
-	 * Calling the method close on a Connection object that is already
-	 * closed is a no-op.
-	 */
-	@Override
-	public void close() {
-		synchronized (server) {
-			for (Statement st : statements.keySet()) {
-				try {
-					st.close();
-				} catch (SQLException e) {
-					// better luck next time!
-				}
-			}
-			// close the socket
-			server.close();
-			// close active SendThread if any
-			if (sendThread != null) {
-				sendThread.shutdown();
-				sendThread = null;
-			}
-			// report ourselves as closed
-			closed = true;
-		}
-	}
+    public abstract void setSoTimeout(int s) throws SocketException;
+
+    public abstract void closeUnderlyingConnection() throws IOException;
+
+    public abstract String getJDBCURL();
 
-	/**
-	 * Makes all changes made since the previous commit/rollback
-	 * permanent and releases any database locks currently held by this
-	 * Connection object.  This method should be used only when
-	 * auto-commit mode has been disabled.
-	 *
-	 * @throws SQLException if a database access error occurs or this
-	 *         Connection object is in auto-commit mode
-	 * @see #setAutoCommit(boolean)
-	 */
-	@Override
-	public void commit() throws SQLException {
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
-
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send commit to the server
-		try {
-			l.processQuery("COMMIT");
-		} finally {
-			l.close();
-		}
-	}
+    /**
+     * Releases this Connection object's database and JDBC resources
+     * immediately instead of waiting for them to be automatically
+     * released. All Statements created from this Connection will be
+     * closed when this method is called.
+     *
+     * Calling the method close on a Connection object that is already
+     * closed is a no-op.
+     */
+    @Override
+    public synchronized void close() {
+        for (Statement st : statements.keySet()) {
+            try {
+                st.close();
+            } catch (SQLException e) {
+                // better luck next time!
+            }
+        }
+        //close the debugger
+        try {
+            if (ourSavior != null) {
+                ourSavior.close();
+            }
+        } catch (IOException e) {
+            // ignore it
+        }
+        // close the socket or the embedded server
+        try {
+            this.closeUnderlyingConnection();
+        } catch (IOException e) {
+            // ignore it
+        }
+        // close active SendThread if any
+        if (sendThread != null) {
+            sendThread.shutdown();
+            sendThread = null;
+        }
+        // report ourselves as closed
+        closed = true;
+    }
 
-	/**
-	 * Factory method for creating Array objects.
-	 * 
-	 * Note: When createArrayOf is used to create an array object that
-	 * maps to a primitive data type, then it is implementation-defined
-	 * whether the Array object is an array of that primitive data type
-	 * or an array of Object.
-	 * 
-	 * Note: The JDBC driver is responsible for mapping the elements
-	 * Object array to the default JDBC SQL type defined in
-	 * java.sql.Types for the given class of Object. The default mapping
-	 * is specified in Appendix B of the JDBC specification. If the
-	 * resulting JDBC type is not the appropriate type for the given
-	 * typeName then it is implementation defined whether an
-	 * SQLException is thrown or the driver supports the resulting
-	 * conversion. 
-	 *
-	 * @param typeName the SQL name of the type the elements of the
-	 *        array map to. The typeName is a database-specific name
-	 *        which may be the name of a built-in type, a user-defined
-	 *        type or a standard SQL type supported by this database.
-	 *        This is the value returned by Array.getBaseTypeName
-	 * @return an Array object whose elements map to the specified SQL
-	 *         type
-	 * @throws SQLException if a database error occurs, the JDBC type
-	 *         is not appropriate for the typeName and the conversion is
-	 *         not supported, the typeName is null or this method is
-	 *         called on a closed connection
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support this data type
-	 */
-	@Override
-	public Array createArrayOf(String typeName, Object[] elements)
-		throws SQLException
-	{
-		throw new SQLFeatureNotSupportedException("createArrayOf(String, Object[]) not supported", "0A000");
-	}
-
-	/**
-	 * Creates a Statement object for sending SQL statements to the
-	 * database.  SQL statements without parameters are normally
-	 * executed using Statement objects. If the same SQL statement is
-	 * executed many times, it may be more efficient to use a
-	 * PreparedStatement object.
-	 * 
-	 * Result sets created using the returned Statement object will by
-	 * default be type TYPE_FORWARD_ONLY and have a concurrency level of
-	 * CONCUR_READ_ONLY.
-	 *
-	 * @return a new default Statement object
-	 * @throws SQLException if a database access error occurs
-	 */
-	@Override
-	public Statement createStatement() throws SQLException {
-		return createStatement(
-					ResultSet.TYPE_FORWARD_ONLY,
-					ResultSet.CONCUR_READ_ONLY,
-					ResultSet.HOLD_CURSORS_OVER_COMMIT);
-	}
+    /**
+     * Destructor called by garbage collector before destroying this
+     * object tries to disconnect the MonetDB connection if it has not
+     * been disconnected already.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        this.close();
+        super.finalize();
+    }
 
-	/**
-	 * Creates a Statement object that will generate ResultSet objects
-	 * with the given type and concurrency. This method is the same as
-	 * the createStatement method above, but it allows the default
-	 * result set type and concurrency to be overridden.
-	 *
-	 * @param resultSetType a result set type; one of
-	 *        ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
-	 *        or ResultSet.TYPE_SCROLL_SENSITIVE
-	 * @param resultSetConcurrency a concurrency type; one of
-	 *        ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
-	 * @return a new Statement object that will generate ResultSet objects with
-	 *         the given type and concurrency
-	 * @throws SQLException if a database access error occurs
-	 */
-	@Override
-	public Statement createStatement(
-			int resultSetType,
-			int resultSetConcurrency)
-		throws SQLException
-	{
-		return createStatement(
-					resultSetType,
-					resultSetConcurrency,
-					ResultSet.HOLD_CURSORS_OVER_COMMIT);
-	}
+    //== methods of interface Connection
+
+    /**
+     * Clears all warnings reported for this Connection object. After a
+     * call to this method, the method getWarnings returns null until a
+     * new warning is reported for this Connection object.
+     */
+    @Override
+    public void clearWarnings() {
+        warnings = null;
+    }
 
-	/**
-	 * Creates a Statement object that will generate ResultSet objects
-	 * with the given type, concurrency, and holdability.  This method
-	 * is the same as the createStatement method above, but it allows
-	 * the default result set type, concurrency, and holdability to be
-	 * overridden.
-	 *
-	 * @param resultSetType one of the following ResultSet constants:
-	 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
-	 * or ResultSet.TYPE_SCROLL_SENSITIVE
-	 * @param resultSetConcurrency one of the following ResultSet
-	 * constants: ResultSet.CONCUR_READ_ONLY or
-	 * ResultSet.CONCUR_UPDATABLE
-	 * @param resultSetHoldability one of the following ResultSet
-	 * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
-	 * ResultSet.CLOSE_CURSORS_AT_COMMIT 
-	 *
-	 * @return a new Statement      object that will generate ResultSet
-	 * objects with the given type, concurrency, and holdability 
-	 * @throws SQLException if a database access error occurs or the
-	 * given parameters are not ResultSet constants indicating type,
-	 * concurrency, and holdability
-	 */
-	@Override
-	public Statement createStatement(
-			int resultSetType,
-			int resultSetConcurrency,
-			int resultSetHoldability)
-		throws SQLException
-	{
-		try {
-			Statement ret =
-				new MonetStatement(
-					this,
-					resultSetType,
-					resultSetConcurrency,
-					resultSetHoldability
-				);
-			// store it in the map for when we close...
-			statements.put(ret, null);
-			return ret;
-		} catch (IllegalArgumentException e) {
-			throw new SQLException(e.toString(), "M0M03");
-		}
-		// we don't have to catch SQLException because that is declared to
-		// be thrown
-	}
+    /**
+     * Makes all changes made since the previous commit/rollback
+     * permanent and releases any database locks currently held by this
+     * Connection object.  This method should be used only when
+     * auto-commit mode has been disabled.
+     *
+     * @throws SQLException if a database access error occurs or this
+     *         Connection object is in auto-commit mode
+     * @see #setAutoCommit(boolean)
+     */
+    @Override
+    public void commit() throws SQLException {
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send commit to the server
+        try {
+            l.processQuery("COMMIT");
+        } finally {
+            l.close();
+        }
+    }
 
-	/**
-	 * Constructs an object that implements the Clob interface. The
-	 * object returned initially contains no data. The setAsciiStream,
-	 * setCharacterStream and setString methods of the Clob interface
-	 * may be used to add data to the Clob.
-	 *
-	 * @return a MonetClob instance
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support MonetClob objects that can be filled in
-	 */
-	@Override
-	public Clob createClob() throws SQLException {
-		throw new SQLFeatureNotSupportedException("createClob() not supported", "0A000");
-	}
-
-	/**
-	 * Constructs an object that implements the Blob interface. The
-	 * object returned initially contains no data. The setBinaryStream
-	 * and setBytes methods of the Blob interface may be used to add
-	 * data to the Blob.
-	 *
-	 * @return a MonetBlob instance
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support MonetBlob objects that can be filled in
-	 */
-	@Override
-	public Blob createBlob() throws SQLException {
-		throw new SQLFeatureNotSupportedException("createBlob() not supported", "0A000");
-	}
-
-	/**
-	 * Constructs an object that implements the NClob interface. The
-	 * object returned initially contains no data. The setAsciiStream,
-	 * setCharacterStream and setString methods of the NClob interface
-	 * may be used to add data to the NClob.
-	 *
-	 * @return an NClob instance
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support MonetClob objects that can be filled in
-	 */
-	@Override
-	public NClob createNClob() throws SQLException {
-		throw new SQLFeatureNotSupportedException("createNClob() not supported", "0A000");
-	}
-
-	/**
-	 * Factory method for creating Struct objects.
-	 *
-	 * @param typeName the SQL type name of the SQL structured type that
-	 *        this Struct object maps to. The typeName is the name of a
-	 *        user-defined type that has been defined for this database.
-	 *        It is the value returned by Struct.getSQLTypeName.
-	 * @param attributes the attributes that populate the returned
-	 *        object
-	 * @return a Struct object that maps to the given SQL type and is
-	 *         populated with the given attributes
-	 * @throws SQLException if a database error occurs, the typeName
-	 *         is null or this method is called on a closed connection
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support this data type
-	 */
-	@Override
-	public Struct createStruct(String typeName, Object[] attributes)
-		throws SQLException
-	{
-		throw new SQLFeatureNotSupportedException("createStruct() not supported", "0A000");
-	}
-
-	/**
-	 * Constructs an object that implements the SQLXML interface. The
-	 * object returned initially contains no data. The
-	 * createXmlStreamWriter object and setString method of the SQLXML
-	 * interface may be used to add data to the SQLXML object.
-	 *
-	 * @return An object that implements the SQLXML interface
-	 * @throws SQLFeatureNotSupportedException the JDBC driver does
-	 *         not support this data type
-	 */
-	@Override
-	public SQLXML createSQLXML() throws SQLException {
-		throw new SQLFeatureNotSupportedException("createSQLXML() not supported", "0A000");
-	}
+    /**
+     * Factory method for creating Array objects.
+     *
+     * Note: When createArrayOf is used to create an array object that
+     * maps to a primitive data type, then it is implementation-defined
+     * whether the Array object is an array of that primitive data type
+     * or an array of Object.
+     *
+     * Note: The JDBC driver is responsible for mapping the elements
+     * Object array to the default JDBC SQL type defined in
+     * java.sql.Types for the given class of Object. The default mapping
+     * is specified in Appendix B of the JDBC specification. If the
+     * resulting JDBC type is not the appropriate type for the given
+     * typeName then it is implementation defined whether an
+     * SQLException is thrown or the driver supports the resulting
+     * conversion.
+     *
+     * @param typeName the SQL name of the type the elements of the
+     *        array map to. The typeName is a database-specific name
+     *        which may be the name of a built-in type, a user-defined
+     *        type or a standard SQL type supported by this database.
+     *        This is the value returned by Array.getBaseTypeName
+     * @return an Array object whose elements map to the specified SQL
+     *         type
+     * @throws SQLException if a database error occurs, the JDBC type
+     *         is not appropriate for the typeName and the conversion is
+     *         not supported, the typeName is null or this method is
+     *         called on a closed connection
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support this data type
+     *
+     * @since 1.6
+     */
+    @Override
+    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+        throw new SQLFeatureNotSupportedException("createArrayOf(String, Object[]) not supported", "0A000");
+    }
 
-	/**
-	 * Retrieves the current auto-commit mode for this Connection
-	 * object.
-	 *
-	 * @return the current state of this Connection object's auto-commit
-	 *         mode
-	 * @see #setAutoCommit(boolean)
-	 */
-	@Override
-	public boolean getAutoCommit() throws SQLException {
-		return autoCommit;
-	}
-
-	/**
-	 * Retrieves this Connection object's current catalog name.
-	 *
-	 * @return the current catalog name or null if there is none
-	 * @throws SQLException if a database access error occurs or the
-	 *         current language is not SQL
-	 */
-	@Override
-	public String getCatalog() throws SQLException {
-		// MonetDB does NOT support catalogs
-		return null;
-	}
-	
-	/**
-	 * Not implemented by MonetDB's JDBC driver.
-	 *
-	 * @param name The name of the client info property to retrieve
-	 * @return The value of the client info property specified
-	 */
-	@Override
-	public String getClientInfo(String name) {
-		// This method will also return null if the specified client
-		// info property name is not supported by the driver.
-		return null;
-	}
-
-	/**
-	 * Not implemented by MonetDB's JDBC driver.
-	 *
-	 * @return A Properties object that contains the name and current
-	 *         value of each of the client info properties supported by
-	 *         the driver.
-	 */
-	@Override
-	public Properties getClientInfo() {
-		return new Properties();
-	}
-
-	/**
-	 * Retrieves the current holdability of ResultSet objects created
-	 * using this Connection object.
-	 *
-	 * @return the holdability, one of
-	 *         ResultSet.HOLD_CURSORS_OVER_COMMIT or
-	 *         ResultSet.CLOSE_CURSORS_AT_COMMIT
-	 */
-	@Override
-	public int getHoldability() {
-		// TODO: perhaps it is better to have the server implement
-		//       CLOSE_CURSORS_AT_COMMIT
-		return ResultSet.HOLD_CURSORS_OVER_COMMIT;
-	}
-
-	/**
-	 * Retrieves a DatabaseMetaData object that contains metadata about
-	 * the database to which this Connection object represents a
-	 * connection. The metadata includes information about the
-	 * database's tables, its supported SQL grammar, its stored
-	 * procedures, the capabilities of this connection, and so on.
-	 *
-	 * @throws SQLException if the current language is not SQL
-	 * @return a DatabaseMetaData object for this Connection object
-	 */
-	@Override
-	public DatabaseMetaData getMetaData() throws SQLException {
-		if (server.getLang() != AbstractMonetDBConnection.LANG_SQL)
-			throw new SQLException("This method is only supported in SQL mode", "M0M04");
-
-		return new MonetDatabaseMetaData(this);
-	}
+    /**
+     * Creates a Statement object for sending SQL statements to the
+     * database.  SQL statements without parameters are normally
+     * executed using Statement objects. If the same SQL statement is
+     * executed many times, it may be more efficient to use a
+     * PreparedStatement object.
+     *
+     * Result sets created using the returned Statement object will by
+     * default be type TYPE_FORWARD_ONLY and have a concurrency level of
+     * CONCUR_READ_ONLY.
+     *
+     * @return a new default Statement object
+     * @throws SQLException if a database access error occurs
+     */
+    @Override
+    public Statement createStatement() throws SQLException {
+        return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
+    }
 
-	/**
-	 * Retrieves this Connection object's current transaction isolation
-	 * level.
-	 *
-	 * @return the current transaction isolation level, which will be
-	 *         Connection.TRANSACTION_SERIALIZABLE
-	 */
-	@Override
-	public int getTransactionIsolation() {
-		return TRANSACTION_SERIALIZABLE;
-	}
-
-	/**
-	 * Retrieves the Map object associated with this Connection object.
-	 * Unless the application has added an entry, the type map returned
-	 * will be empty.
-	 *
-	 * @return the java.util.Map object associated with this Connection
-	 *         object
-	 */
-	@Override
-	public Map<String,Class<?>> getTypeMap() {
-		return typeMap;
-	}
+    /**
+     * Creates a Statement object that will generate ResultSet objects
+     * with the given type and concurrency. This method is the same as
+     * the createStatement method above, but it allows the default
+     * result set type and concurrency to be overridden.
+     *
+     * @param resultSetType a result set type; one of
+     *        ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
+     *        or ResultSet.TYPE_SCROLL_SENSITIVE
+     * @param resultSetConcurrency a concurrency type; one of
+     *        ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
+     * @return a new Statement object that will generate ResultSet objects with
+     *         the given type and concurrency
+     * @throws SQLException if a database access error occurs
+     */
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+        return createStatement(resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
+    }
 
-	/**
-	 * Retrieves the first warning reported by calls on this Connection
-	 * object.  If there is more than one warning, subsequent warnings
-	 * will be chained to the first one and can be retrieved by calling
-	 * the method SQLWarning.getNextWarning on the warning that was
-	 * retrieved previously.
-	 * 
-	 * This method may not be called on a closed connection; doing so
-	 * will cause an SQLException to be thrown.
-	 * 
-	 * Note: Subsequent warnings will be chained to this SQLWarning.
-	 *
-	 * @return the first SQLWarning object or null if there are none
-	 * @throws SQLException if a database access error occurs or this method is
-	 *         called on a closed connection
-	 */
-	@Override
-	public SQLWarning getWarnings() throws SQLException {
-		if (closed)
-			throw new SQLException("Cannot call on closed Connection", "M1M20");
-
-		// if there are no warnings, this will be null, which fits with the
-		// specification.
-		return warnings;
-	}
-
-	/**
-	 * Retrieves whether this Connection object has been closed.  A
-	 * connection is closed if the method close has been called on it or
-	 * if certain fatal errors have occurred.  This method is guaranteed
-	 * to return true only when it is called after the method
-	 * Connection.close has been called.
-	 * 
-	 * This method generally cannot be called to determine whether a
-	 * connection to a database is valid or invalid.  A typical client
-	 * can determine that a connection is invalid by catching any
-	 * exceptions that might be thrown when an operation is attempted.
-	 *
-	 * @return true if this Connection object is closed; false if it is
-	 *         still open
-	 */
-	@Override
-	public boolean isClosed() {
-		return closed;
-	}
-
-	/**
-	 * Retrieves whether this Connection object is in read-only mode.
-	 * MonetDB currently doesn't support updateable result sets, but
-	 * updates are possible.  Hence the Connection object is never in
-	 * read-only mode.
-	 *
-	 * @return true if this Connection object is read-only; false otherwise
-	 */
-	@Override
-	public boolean isReadOnly() {
-		return false;
-	}
+    /**
+     * Creates a Statement object that will generate ResultSet objects
+     * with the given type, concurrency, and holdability.  This method
+     * is the same as the createStatement method above, but it allows
+     * the default result set type, concurrency, and holdability to be
+     * overridden.
+     *
+     * @param resultSetType one of the following ResultSet constants:
+     * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
+     * or ResultSet.TYPE_SCROLL_SENSITIVE
+     * @param resultSetConcurrency one of the following ResultSet
+     * constants: ResultSet.CONCUR_READ_ONLY or
+     * ResultSet.CONCUR_UPDATABLE
+     * @param resultSetHoldability one of the following ResultSet
+     * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
+     * ResultSet.CLOSE_CURSORS_AT_COMMIT
+     *
+     * @return a new Statement      object that will generate ResultSet
+     * objects with the given type, concurrency, and holdability
+     * @throws SQLException if a database access error occurs or the
+     * given parameters are not ResultSet constants indicating type,
+     * concurrency, and holdability
+     */
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
+        try {
+            Statement ret = new MonetStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
+            // store it in the map for when we close...
+            statements.put(ret, null);
+            return ret;
+        } catch (IllegalArgumentException e) {
+            throw new SQLException(e.toString(), "M0M03");
+        }
+        // we don't have to catch SQLException because that is declared to
+        // be thrown
+    }
 
-	/**
-	 * Returns true if the connection has not been closed and is still
-	 * valid. The driver shall submit a query on the connection or use
-	 * some other mechanism that positively verifies the connection is
-	 * still valid when this method is called.
-	 * 
-	 * The query submitted by the driver to validate the connection
-	 * shall be executed in the context of the current transaction.
-	 *
-	 * @param timeout The time in seconds to wait for the database
-	 *        operation used to validate the connection to complete. If
-	 *        the timeout period expires before the operation completes,
-	 *        this method returns false. A value of 0 indicates a
-	 *        timeout is not applied to the database operation.
-	 * @return true if the connection is valid, false otherwise
-	 * @throws SQLException if the value supplied for timeout is less
-	 *         than 0
-	 */
-	@Override
-	public boolean isValid(int timeout) throws SQLException {
-		if (timeout < 0)
-			throw new SQLException("timeout is less than 0", "M1M05");
-		if (closed)
-			return false;
-		// ping db using select 1;
-		Statement stmt = null;
-		try {
-			stmt = createStatement();
-			// the timeout parameter is ignored here, since
-			// MonetStatement.setQueryTimeout(timeout) is not supported.
-			stmt.executeQuery("SELECT 1");
-			stmt.close();
-			return true;
-		} catch (Exception e) {
-			if (stmt != null) {
-				try {
-					stmt.close();
-				} catch (Exception e2) {}
-			}
-		}
-		return false;
-	}
+    /**
+     * Constructs an object that implements the Clob interface. The
+     * object returned initially contains no data. The setAsciiStream,
+     * setCharacterStream and setString methods of the Clob interface
+     * may be used to add data to the Clob.
+     *
+     * @return a MonetClob instance
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support MonetClob objects that can be filled in
+     * @since 1.6
+     */
+    @Override
+    public Clob createClob() throws SQLException {
+        return new MonetClob("");
+    }
 
-	@Override
-	public String nativeSQL(String sql) {return sql;}
-	@Override
-	public CallableStatement prepareCall(String sql) {return null;}
-	@Override
-	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {return null;}
-	@Override
-	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {return null;}
+    /**
+     * Constructs an object that implements the Blob interface. The
+     * object returned initially contains no data. The setBinaryStream
+     * and setBytes methods of the Blob interface may be used to add
+     * data to the Blob.
+     *
+     * @return a MonetBlob instance
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support MonetBlob objects that can be filled in
+     */
+    @Override
+    public Blob createBlob() throws SQLException {
+        throw new SQLFeatureNotSupportedException("createBlob() not supported", "0A000");
+    }
 
-	/**
-	 * Creates a PreparedStatement object for sending parameterized SQL
-	 * statements to the database.
-	 * 
-	 * A SQL statement with or without IN parameters can be pre-compiled
-	 * and stored in a PreparedStatement object. This object can then be
-	 * used to efficiently execute this statement multiple times.
-	 * 
-	 * Note: This method is optimized for handling parametric SQL
-	 * statements that benefit from precompilation. If the driver
-	 * supports precompilation, the method prepareStatement will send
-	 * the statement to the database for precompilation. Some drivers
-	 * may not support precompilation. In this case, the statement may
-	 * not be sent to the database until the PreparedStatement object is
-	 * executed. This has no direct effect on users; however, it does
-	 * affect which methods throw certain SQLException objects.
-	 * 
-	 * Result sets created using the returned PreparedStatement object
-	 * will by default be type TYPE_FORWARD_ONLY and have a concurrency
-	 * level of CONCUR_READ_ONLY.
-	 *
-	 * @param sql an SQL statement that may contain one or more '?' IN
-	 *        parameter placeholders
-	 * @return a new default PreparedStatement object containing the
-	 *         pre-compiled SQL statement
-	 * @throws SQLException if a database access error occurs
-	 */
-	@Override
-	public PreparedStatement prepareStatement(String sql) throws SQLException {
-		return prepareStatement(
-					sql,
-					ResultSet.TYPE_FORWARD_ONLY,
-					ResultSet.CONCUR_READ_ONLY,
-					ResultSet.HOLD_CURSORS_OVER_COMMIT
-		);
-	}
+    /**
+     * Constructs an object that implements the NClob interface. The
+     * object returned initially contains no data. The setAsciiStream,
+     * setCharacterStream and setString methods of the NClob interface
+     * may be used to add data to the NClob.
+     *
+     * @return an NClob instance
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support MonetClob objects that can be filled in
+     */
+    @Override
+    public NClob createNClob() throws SQLException {
+        throw new SQLFeatureNotSupportedException("createNClob() not supported", "0A000");
+    }
 
-	/**
-	 * Creates a PreparedStatement object that will generate ResultSet
-	 * objects with the given type and concurrency.  This method is the
-	 * same as the prepareStatement method above, but it allows the
-	 * default result set type and concurrency to be overridden.
-	 *
-	 * @param sql a String object that is the SQL statement to be sent to the
-	 *            database; may contain one or more ? IN parameters
-	 * @param resultSetType a result set type; one of
-	 *        ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
-	 *        or ResultSet.TYPE_SCROLL_SENSITIVE
-	 * @param resultSetConcurrency a concurrency type; one of
-	 *        ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
-	 * @return a new PreparedStatement object containing the pre-compiled SQL
-	 *         statement that will produce ResultSet objects with the given
-	 *         type and concurrency
-	 * @throws SQLException if a database access error occurs or the given
-	 *                      parameters are not ResultSet constants indicating
-	 *                      type and concurrency
-	 */
-	@Override
-	public PreparedStatement prepareStatement(
-			String sql,
-			int resultSetType,
-			int resultSetConcurrency)
-		throws SQLException
-	{
-		return prepareStatement(
-					sql,
-					resultSetType,
-					resultSetConcurrency,
-					ResultSet.HOLD_CURSORS_OVER_COMMIT
-		);
-	}
+    /**
+     * Factory method for creating Struct objects.
+     *
+     * @param typeName the SQL type name of the SQL structured type that
+     *        this Struct object maps to. The typeName is the name of a
+     *        user-defined type that has been defined for this database.
+     *        It is the value returned by Struct.getSQLTypeName.
+     * @param attributes the attributes that populate the returned
+     *        object
+     * @return a Struct object that maps to the given SQL type and is
+     *         populated with the given attributes
+     * @throws SQLException if a database error occurs, the typeName
+     *         is null or this method is called on a closed connection
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support this data type
+     */
+    @Override
+    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+        throw new SQLFeatureNotSupportedException("createStruct() not supported", "0A000");
+    }
 
-	/**
-	 * Creates a PreparedStatement object that will generate ResultSet
-	 * objects with the given type, concurrency, and holdability.
-	 * 
-	 * This method is the same as the prepareStatement method above, but
-	 * it allows the default result set type, concurrency, and
-	 * holdability to be overridden.
-	 *
-	 * @param sql a String object that is the SQL statement to be sent
-	 * to the database; may contain one or more ? IN parameters
-	 * @param resultSetType one of the following ResultSet constants:
-	 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
-	 * or ResultSet.TYPE_SCROLL_SENSITIVE
-	 * @param resultSetConcurrency one of the following ResultSet
-	 * constants: ResultSet.CONCUR_READ_ONLY or
-	 * ResultSet.CONCUR_UPDATABLE
-	 * @param resultSetHoldability one of the following ResultSet
-	 * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
-	 * ResultSet.CLOSE_CURSORS_AT_COMMIT 
-	 * @return a new PreparedStatement object, containing the
-	 * pre-compiled SQL statement, that will generate ResultSet objects
-	 * with the given type, concurrency, and holdability 
-	 * @throws SQLException if a database access error occurs or the
-	 * given parameters are not ResultSet constants indicating type,
-	 * concurrency, and holdability
-	 */
-	@Override
-	public PreparedStatement prepareStatement(
-			String sql,
-			int resultSetType,
-			int resultSetConcurrency,
-			int resultSetHoldability)
-		throws SQLException
-	{
-		try {
-			PreparedStatement ret = new MonetPreparedStatement(
-				this,
-				resultSetType,
-				resultSetConcurrency,
-				resultSetHoldability,
-				sql
-			);
-			// store it in the map for when we close...
-			statements.put(ret, null);
-			return ret;
-		} catch (IllegalArgumentException e) {
-			throw new SQLException(e.toString(), "M0M03");
-		}
-		// we don't have to catch SQLException because that is declared to
-		// be thrown
-	}
+    /**
+     * Constructs an object that implements the SQLXML interface. The
+     * object returned initially contains no data. The
+     * createXmlStreamWriter object and setString method of the SQLXML
+     * interface may be used to add data to the SQLXML object.
+     *
+     * @return An object that implements the SQLXML interface
+     * @throws SQLFeatureNotSupportedException the JDBC driver does
+     *         not support this data type
+     */
+    @Override
+    public SQLXML createSQLXML() throws SQLException {
+        throw new SQLFeatureNotSupportedException("createSQLXML() not supported", "0A000");
+    }
+
+    /**
+     * Retrieves the current auto-commit mode for this Connection
+     * object.
+     *
+     * @return the current state of this Connection object's auto-commit
+     *         mode
+     * @see #setAutoCommit(boolean)
+     */
+    @Override
+    public boolean getAutoCommit() throws SQLException {
+        return autoCommit;
+    }
 
-	/**
-	 * Creates a default PreparedStatement object that has the
-	 * capability to retrieve auto-generated keys.  The given constant
-	 * tells the driver whether it should make auto-generated keys
-	 * available for retrieval.  This parameter is ignored if the SQL
-	 * statement is not an INSERT statement.
-	 * 
-	 * Note: This method is optimized for handling parametric SQL
-	 * statements that benefit from precompilation.  If the driver
-	 * supports precompilation, the method prepareStatement will send
-	 * the statement to the database for precompilation. Some drivers
-	 * may not support precompilation.  In this case, the statement may
-	 * not be sent to the database until the PreparedStatement object is
-	 * executed.  This has no direct effect on users; however, it does
-	 * affect which methods throw certain SQLExceptions.
-	 * 
-	 * Result sets created using the returned PreparedStatement object
-	 * will by default be type TYPE_FORWARD_ONLY and have a concurrency
-	 * level of CONCUR_READ_ONLY.
-	 *
-	 * @param sql an SQL statement that may contain one or more '?' IN
-	 *        parameter placeholders
-	 * @param autoGeneratedKeys a flag indicating whether auto-generated
-	 *        keys should be returned; one of
-	 *        Statement.RETURN_GENERATED_KEYS or
-	 *        Statement.NO_GENERATED_KEYS
-	 * @return a new PreparedStatement object, containing the
-	 *         pre-compiled SQL statement, that will have the capability
-	 *         of returning auto-generated keys
-	 * @throws SQLException - if a database access error occurs or the
-	 *         given parameter is not a Statement  constant indicating
-	 *         whether auto-generated keys should be returned
-	 */
-	@Override
-	public PreparedStatement prepareStatement(
-			String sql,
-			int autoGeneratedKeys)
-		throws SQLException
-	{
-		if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS &&
-				autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
-			throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05");
-		
-		/* MonetDB has no way to disable this, so just do the normal
-		 * thing ;) */
-		return prepareStatement(
-					sql,
-					ResultSet.TYPE_FORWARD_ONLY,
-					ResultSet.CONCUR_READ_ONLY
-		);
-	}
+    /**
+     * Retrieves this Connection object's current catalog name.
+     *
+     * @return the current catalog name or null if there is none
+     * @throws SQLException if a database access error occurs or the
+     *         current language is not SQL
+     */
+    @Override
+    public String getCatalog() throws SQLException {
+        // MonetDB does NOT support catalogs
+        return null;
+    }
 
-	@Override
-	public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {return null;}
-	@Override
-	public PreparedStatement prepareStatement(String sql, String[] columnNames) {return null;}
+    /**
+     * Not implemented by MonetDB's JDBC driver.
+     *
+     * @param name The name of the client info property to retrieve
+     * @return The value of the client info property specified
+     */
+    @Override
+    public String getClientInfo(String name) {
+        // This method will also return null if the specified client
+        // info property name is not supported by the driver.
+        return null;
+    }
 
-	/**
-	 * Removes the given Savepoint object from the current transaction.
-	 * Any reference to the savepoint after it have been removed will
-	 * cause an SQLException to be thrown.
-	 *
-	 * @param savepoint the Savepoint object to be removed
-	 * @throws SQLException if a database access error occurs or the given
-	 *         Savepoint object is not a valid savepoint in the current
-	 *         transaction
-	 */
-	@Override
-	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
-		if (!(savepoint instanceof MonetSavepoint)) throw
-			new SQLException("This driver can only handle savepoints it created itself", "M0M06");
-
-		MonetSavepoint sp = (MonetSavepoint)savepoint;
-
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
-
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send the appropriate query string to the database
-		try {
-			l.processQuery("RELEASE SAVEPOINT " + sp.getName());
-		} finally {
-			l.close();
-		}
-	}
+    /**
+     * Not implemented by MonetDB's JDBC driver.
+     *
+     * @return A Properties object that contains the name and current
+     *         value of each of the client info properties supported by
+     *         the driver.
+     */
+    @Override
+    public Properties getClientInfo() {
+        return new Properties();
+    }
 
-	/**
-	 * Undoes all changes made in the current transaction and releases
-	 * any database locks currently held by this Connection object. This
-	 * method should be used only when auto-commit mode has been
-	 * disabled.
-	 *
-	 * @throws SQLException if a database access error occurs or this
-	 *         Connection object is in auto-commit mode
-	 * @see #setAutoCommit(boolean)
-	 */
-	@Override
-	public void rollback() throws SQLException {
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
-
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send rollback to the server
-		try {
-			l.processQuery("ROLLBACK");
-		} finally {
-			l.close();
-		}
-	}
-
-	/**
-	 * Undoes all changes made after the given Savepoint object was set.
-	 * 
-	 * This method should be used only when auto-commit has been
-	 * disabled.
-	 *
-	 * @param savepoint the Savepoint object to roll back to
-	 * @throws SQLException if a database access error occurs, the
-	 *         Savepoint object is no longer valid, or this Connection
-	 *         object is currently in auto-commit mode
-	 */
-	@Override
-	public void rollback(Savepoint savepoint) throws SQLException {
-		if (!(savepoint instanceof MonetSavepoint)) throw
-			new SQLException("This driver can only handle savepoints it created itself", "M0M06");
-
-		MonetSavepoint sp = (MonetSavepoint)savepoint;
-
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
+    /**
+     * Retrieves the current holdability of ResultSet objects created
+     * using this Connection object.
+     *
+     * @return the holdability, one of
+     *         ResultSet.HOLD_CURSORS_OVER_COMMIT or
+     *         ResultSet.CLOSE_CURSORS_AT_COMMIT
+     */
+    @Override
+    public int getHoldability() {
+        // TODO: perhaps it is better to have the server implement
+        //       CLOSE_CURSORS_AT_COMMIT
+        return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+    }
 
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send the appropriate query string to the database
-		try {
-			l.processQuery("ROLLBACK TO SAVEPOINT " + sp.getName());
-		} finally {
-			l.close();
-		}
-	}
+    /**
+     * Retrieves a DatabaseMetaData object that contains metadata about
+     * the database to which this Connection object represents a
+     * connection. The metadata includes information about the
+     * database's tables, its supported SQL grammar, its stored
+     * procedures, the capabilities of this connection, and so on.
+     *
+     * @throws SQLException if the current language is not SQL
+     * @return a DatabaseMetaData object for this Connection object
+     */
+    @Override
+    public DatabaseMetaData getMetaData() throws SQLException {
+        if (this.currentMonetDBLanguage != MonetDBLanguage.LANG_SQL) {
+            throw new SQLException("This method is only supported in SQL mode", "M0M04");
+        }
+        return new MonetDatabaseMetaData(this);
+    }
 
-	/**
-	 * Sets this connection's auto-commit mode to the given state. If a
-	 * connection is in auto-commit mode, then all its SQL statements
-	 * will be executed and committed as individual transactions.
-	 * Otherwise, its SQL statements are grouped into transactions that
-	 * are terminated by a call to either the method commit or the
-	 * method rollback. By default, new connections are in auto-commit
-	 * mode.
-	 * 
-	 * The commit occurs when the statement completes or the next
-	 * execute occurs, whichever comes first. In the case of statements
-	 * returning a ResultSet object, the statement completes when the
-	 * last row of the ResultSet object has been retrieved or the
-	 * ResultSet object has been closed. In advanced cases, a single
-	 * statement may return multiple results as well as output parameter
-	 * values. In these cases, the commit occurs when all results and
-	 * output parameter values have been retrieved.
-	 * 
-	 * NOTE: If this method is called during a transaction, the
-	 * transaction is committed.
-	 *
-	 * @param autoCommit true to enable auto-commit mode; false to disable it
-	 * @throws SQLException if a database access error occurs
-	 * @see #getAutoCommit()
-	 */
-	@Override
-	public void setAutoCommit(boolean autoCommit) throws SQLException {
-		if (this.autoCommit != autoCommit) {
-			sendControlCommand("auto_commit " + (autoCommit ? "1" : "0"));
-			this.autoCommit = autoCommit;
-		}
-	}
-
-	/**
-	 * Sets the given catalog name in order to select a subspace of this
-	 * Connection object's database in which to work.  If the driver
-	 * does not support catalogs, it will silently ignore this request. 
-	 */
-	@Override
-	public void setCatalog(String catalog) throws SQLException {
-		// silently ignore this request as MonetDB does not support catalogs
-	}
-
-	/**
-	 * Not implemented by MonetDB's JDBC driver.
-	 *
-	 * @param name The name of the client info property to set
-	 * @param value The value to set the client info property to. If the
-	 *        value is null, the current value of the specified property
-	 *        is cleared.
-	 */
-	@Override
-	public void setClientInfo(String name, String value) {
-		addWarning("clientInfo: " + name + "is not a recognised property", "01M07");
-	}
-
-	/**
-	 * Not implemented by MonetDB's JDBC driver.
-	 *
-	 * @param props The list of client info properties to set
-	 */
-	@Override
-	public void setClientInfo(Properties props) {
-		for (Entry<Object, Object> entry : props.entrySet()) {
-			setClientInfo(entry.getKey().toString(),
-					entry.getValue().toString());
-		}
-	}
+    /**
+     * Retrieves this Connection object's current transaction isolation
+     * level.
+     *
+     * @return the current transaction isolation level, which will be
+     *         Connection.TRANSACTION_SERIALIZABLE
+     */
+    @Override
+    public int getTransactionIsolation() {
+        return TRANSACTION_SERIALIZABLE;
+    }
 
-	@Override
-	public void setHoldability(int holdability) {}
-
-	/**
-	 * Puts this connection in read-only mode as a hint to the driver to
-	 * enable database optimizations.  MonetDB doesn't support any mode
-	 * here, hence an SQLWarning is generated if attempted to set
-	 * to true here.
-	 *
-	 * @param readOnly true enables read-only mode; false disables it
-	 * @throws SQLException if a database access error occurs or this
-	 *         method is called during a transaction.
-	 */
-	@Override
-	public void setReadOnly(boolean readOnly) throws SQLException {
-		if (readOnly)
-			addWarning("cannot setReadOnly(true): read-only Connection mode not supported", "01M08");
-	}
-
-	/**
-	 * Creates an unnamed savepoint in the current transaction and
-	 * returns the new Savepoint object that represents it.
-	 *
-	 * @return the new Savepoint object
-	 * @throws SQLException if a database access error occurs or this Connection
-	 *         object is currently in auto-commit mode
-	 */
-	@Override
-	public Savepoint setSavepoint() throws SQLException {
-		// create a new Savepoint object
-		MonetSavepoint sp = new MonetSavepoint();
-
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
+    /**
+     * Retrieves the Map object associated with this Connection object.
+     * Unless the application has added an entry, the type map returned
+     * will be empty.
+     *
+     * @return the java.util.Map object associated with this Connection
+     *         object
+     */
+    @Override
+    public Map<String,Class<?>> getTypeMap() {
+        return typeMap;
+    }
 
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send the appropriate query string to the database
-		try {
-			l.processQuery("SAVEPOINT " + sp.getName());
-		} finally {
-			l.close();
-		}
-
-		return sp;
-	}
-
-	/**
-	 * Creates a savepoint with the given name in the current
-	 * transaction and returns the new Savepoint object that represents
-	 * it.
-	 *
-	 * @param name a String containing the name of the savepoint
-	 * @return the new Savepoint object
-	 * @throws SQLException if a database access error occurs or this Connection
-	 *         object is currently in auto-commit mode
-	 */
-	@Override
-	public Savepoint setSavepoint(String name) throws SQLException {
-		// create a new Savepoint object
-		MonetSavepoint sp;
-		try {
-			sp = new MonetSavepoint(name);
-		} catch (IllegalArgumentException e) {
-			throw new SQLException(e.getMessage(), "M0M03");
-		}
-
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
-
-		// create a container for the result
-		ResponseList l = new ResponseList(
-			0,
-			0,
-			ResultSet.FETCH_FORWARD,
-			ResultSet.CONCUR_READ_ONLY
-		);
-		// send the appropriate query string to the database
-		try {
-			l.processQuery("SAVEPOINT " + sp.getName());
-		} finally {
-			l.close();
-		}
+    /**
+     * Retrieves the first warning reported by calls on this Connection
+     * object.  If there is more than one warning, subsequent warnings
+     * will be chained to the first one and can be retrieved by calling
+     * the method SQLWarning.getNextWarning on the warning that was
+     * retrieved previously.
+     *
+     * This method may not be called on a closed connection; doing so
+     * will cause an SQLException to be thrown.
+     *
+     * Note: Subsequent warnings will be chained to this SQLWarning.
+     *
+     * @return the first SQLWarning object or null if there are none
+     * @throws SQLException if a database access error occurs or this method is
+     *         called on a closed connection
+     */
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        if (closed) {
+            throw new SQLException("Cannot call on closed Connection", "M1M20");
+        }
+        // if there are no warnings, this will be null, which fits with the
+        // specification.
+        return warnings;
+    }
 
-		return sp;
-	}
-
-	/**
-	 * Attempts to change the transaction isolation level for this
-	 * Connection object to the one given.  The constants defined in the
-	 * interface Connection are the possible transaction isolation
-	 * levels.
-	 *
-	 * @param level one of the following Connection constants:
-	 *        Connection.TRANSACTION_READ_UNCOMMITTED,
-	 *        Connection.TRANSACTION_READ_COMMITTED,
-	 *        Connection.TRANSACTION_REPEATABLE_READ, or
-	 *        Connection.TRANSACTION_SERIALIZABLE.
-	 */
-	@Override
-	public void setTransactionIsolation(int level) {
-		if (level != TRANSACTION_SERIALIZABLE) {
-			addWarning("MonetDB only supports fully serializable " +
-					"transactions, continuing with transaction level " +
-					"raised to TRANSACTION_SERIALIZABLE", "01M09");
-		}
-	}
-
-	/**
-	 * Installs the given TypeMap object as the type map for this
-	 * Connection object. The type map will be used for the custom
-	 * mapping of SQL structured types and distinct types.
-	 *
-	 * @param map the java.util.Map object to install as the replacement for
-	 *        this Connection  object's default type map
-	 */
-	@Override
-	public void setTypeMap(Map<String, Class<?>> map) {
-		typeMap = map;
-	}
+    /**
+     * Retrieves whether this Connection object has been closed.  A
+     * connection is closed if the method close has been called on it or
+     * if certain fatal errors have occurred.  This method is guaranteed
+     * to return true only when it is called after the method
+     * Connection.close has been called.
+     *
+     * This method generally cannot be called to determine whether a
+     * connection to a database is valid or invalid.  A typical client
+     * can determine that a connection is invalid by catching any
+     * exceptions that might be thrown when an operation is attempted.
+     *
+     * @return true if this Connection object is closed; false if it is
+     *         still open
+     */
+    @Override
+    public boolean isClosed() {
+        return closed;
+    }
 
-	/**
-	 * Returns a string identifying this Connection to the MonetDB
-	 * server.
-	 *
-	 * @return a String representing this Object
-	 */
-	@Override
-	public String toString() {
-		return "MonetDB Connection (" + getJDBCURL() + ") " + 
-				(closed ? "connected" : "disconnected");
-	}
-
-	//== 1.7 methods (JDBC 4.1)
-
-	/**
-	 * Sets the given schema name to access.
-	 *
-	 * @param schema the name of a schema in which to work
-	 * @throws SQLException if a database access error occurs or this
-	 *         method is called on a closed connection
-	 */
-	@Override
-	public void setSchema(String schema) throws SQLException {
-		if (closed)
-			throw new SQLException("Cannot call on closed Connection", "M1M20");
-		if (schema != null)
-			createStatement().execute("SET SCHEMA \"" + schema + "\"");
-	}
-
-	/**
-	 * Retrieves this Connection object's current schema name.
-	 *
-	 * @return the current schema name or null if there is none
-	 * @throws SQLException if a database access error occurs or this
-	 *         method is called on a closed connection
-	 */
-	@Override
-	public String getSchema() throws SQLException {
-		if (closed)
-			throw new SQLException("Cannot call on closed Connection", "M1M20");
+    /**
+     * Retrieves whether this Connection object is in read-only mode.
+     * MonetDB currently doesn't support updateable result sets, but
+     * updates are possible.  Hence the Connection object is never in
+     * read-only mode.
+     *
+     * @return true if this Connection object is read-only; false otherwise
+     */
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
 
-		String cur_schema;
-		Statement st = createStatement();
-		ResultSet rs = null;
-		try {
-			rs = st.executeQuery("SELECT CURRENT_SCHEMA");
-			if (!rs.next())
-				throw new SQLException("Row expected", "02000");
-			cur_schema = rs.getString(1);
-		} finally {
-			if (rs != null)
-				rs.close();
-			st.close();
-		}
-		return cur_schema;
-	}
-
-	/**
-	 * Terminates an open connection. Calling abort results in:
-	 *  * The connection marked as closed
-	 *  * Closes any physical connection to the database
-	 *  * Releases resources used by the connection
-	 *  * Insures that any thread that is currently accessing the
-	 *    connection will either progress to completion or throw an
-	 *    SQLException. 
-	 * Calling abort marks the connection closed and releases any
-	 * resources. Calling abort on a closed connection is a no-op. 
-	 *
-	 * @param executor The Executor implementation which will be used by
-	 *        abort
-	 * @throws SQLException if a database access error occurs or the
-	 *         executor is null
-	 * @throws SecurityException if a security manager exists and
-	 *         its checkPermission method denies calling abort
-	 */
-	@Override
-	public void abort(Executor executor) throws SQLException {
-		if (closed)
-			return;
-		if (executor == null)
-			throw new SQLException("executor is null", "M1M05");
-		// this is really the simplest thing to do, it destroys
-		// everything (in particular the server connection)
-		close();
-	}
+    /**
+     * Returns true if the connection has not been closed and is still
+     * valid. The driver shall submit a query on the connection or use
+     * some other mechanism that positively verifies the connection is
+     * still valid when this method is called.
+     *
+     * The query submitted by the driver to validate the connection
+     * shall be executed in the context of the current transaction.
+     *
+     * @param timeout The time in seconds to wait for the database
+     *        operation used to validate the connection to complete. If
+     *        the timeout period expires before the operation completes,
+     *        this method returns false. A value of 0 indicates a
+     *        timeout is not applied to the database operation.
+     * @return true if the connection is valid, false otherwise
+     * @throws SQLException if the value supplied for timeout is less
+     *         than 0
+     */
+    @Override
+    public boolean isValid(int timeout) throws SQLException {
+        if (timeout < 0)
+            throw new SQLException("timeout is less than 0", "M1M05");
+        if (closed)
+            return false;
+        // ping db using select 1;
+        Statement stmt = null;
+        try {
+            stmt = createStatement();
+            // the timeout parameter is ignored here, since
+            // MonetStatement.setQueryTimeout(timeout) is not supported.
+            stmt.executeQuery("SELECT 1");
+            stmt.close();
+            return true;
+        } catch (Exception e) {
+            if (stmt != null) {
+                try {
+                    stmt.close();
+                } catch (Exception e2) {}
+            }
+        }
+        return false;
+    }
 
-	/**
-	 * Sets the maximum period a Connection or objects created from the
-	 * Connection will wait for the database to reply to any one
-	 * request. If any request remains unanswered, the waiting method
-	 * will return with a SQLException, and the Connection or objects
-	 * created from the Connection will be marked as closed. Any
-	 * subsequent use of the objects, with the exception of the close,
-	 * isClosed or Connection.isValid methods, will result in a
-	 * SQLException.
-	 *
-	 * @param executor The Executor implementation which will be used by
-	 *        setNetworkTimeout
-	 * @param millis The time in milliseconds to wait for the
-	 *        database operation to complete
-	 * @throws SQLException if a database access error occurs, this
-	 *         method is called on a closed connection, the executor is
-	 *         null, or the value specified for seconds is less than 0.
-	 */
-	@Override
-	public void setNetworkTimeout(Executor executor, int millis)
-		throws SQLException
-	{
-		if (closed)
-			throw new SQLException("Cannot call on closed Connection", "M1M20");
-		if (executor == null)
-			throw new SQLException("executor is null", "M1M05");
-		if (millis < 0)
-			throw new SQLException("milliseconds is less than zero", "M1M05");
+    @Override
+    public String nativeSQL(String sql) {return sql;}
+
+    @Override
+    public CallableStatement prepareCall(String sql) {return null;}
 
-		try {
-			server.setSoTimeout(millis);
-		} catch (SocketException e) {
-			throw new SQLException(e.getMessage(), "08000");
-		}
-	}
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {return null;}
 
-	/**
-	 * Retrieves the number of milliseconds the driver will wait for a
-	 * database request to complete. If the limit is exceeded, a
-	 * SQLException is thrown.
-	 *
-	 * @return the current timeout limit in milliseconds; zero means
-	 *         there is no limit
-	 * @throws SQLException if a database access error occurs or
-	 *         this method is called on a closed Connection
-	 */
-	@Override
-	public int getNetworkTimeout() throws SQLException {
-		if (closed)
-			throw new SQLException("Cannot call on closed Connection", "M1M20");
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {return null;}
 
-		try {
-			return server.getSoTimeout();
-		} catch (SocketException e) {
-			throw new SQLException(e.getMessage(), "08000");
-		}
-	}
-
-	//== end methods of interface Connection
-
-	public String getJDBCURL() {
-		return server.getJDBCURL();
-	}
-
-	/**
-	 * Returns whether the BLOB type should be mapped to BINARY type.
-	 */
-	boolean getBlobAsBinary() {
-		return blobIsBinary;
-	}
-
-	/**
-	 * Sends the given string to MonetDB as regular statement, making
-	 * sure there is a prompt after the command is sent.  All possible
-	 * returned information is discarded.  Encountered errors are
-	 * reported.
-	 *
-	 * @param command the exact string to send to MonetDB
-	 * @throws SQLException if an IO exception or a database error occurs
-	 */
-	void sendIndependentCommand(String command) throws SQLException {
-		synchronized (server) {
-			try {
-				out.writeLine(server.getQueryTemplateHeader(0) + command + server.getQueryTemplateHeader(1));
-				String error = in.waitForPrompt();
-				if (error != null)
-					throw new SQLException(error.substring(6),
-							error.substring(0, 5));
-			} catch (SocketTimeoutException e) {
-				close(); // JDBC 4.1 semantics: abort()
-				throw new SQLException("connection timed out", "08M33");
-			} catch (IOException e) {
-				throw new SQLException(e.getMessage(), "08000");
-			}
-		}
-	}
+    /**
+     * Creates a PreparedStatement object for sending parameterized SQL
+     * statements to the database.
+     *
+     * A SQL statement with or without IN parameters can be pre-compiled
+     * and stored in a PreparedStatement object. This object can then be
+     * used to efficiently execute this statement multiple times.
+     *
+     * Note: This method is optimized for handling parametric SQL
+     * statements that benefit from precompilation. If the driver
+     * supports precompilation, the method prepareStatement will send
+     * the statement to the database for precompilation. Some drivers
+     * may not support precompilation. In this case, the statement may
+     * not be sent to the database until the PreparedStatement object is
+     * executed. This has no direct effect on users; however, it does
+     * affect which methods throw certain SQLException objects.
+     *
+     * Result sets created using the returned PreparedStatement object
+     * will by default be type TYPE_FORWARD_ONLY and have a concurrency
+     * level of CONCUR_READ_ONLY.
+     *
+     * @param sql an SQL statement that may contain one or more '?' IN
+     *        parameter placeholders
+     * @return a new default PreparedStatement object containing the
+     *         pre-compiled SQL statement
+     * @throws SQLException if a database access error occurs
+     */
+    @Override
+    public PreparedStatement prepareStatement(String sql) throws SQLException {
+        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
+    }
 
-	/**
-	 * Sends the given string to MonetDB as control statement, making
-	 * sure there is a prompt after the command is sent.  All possible
-	 * returned information is discarded.  Encountered errors are
-	 * reported.
-	 *
-	 * @param command the exact string to send to MonetDB
-	 * @throws SQLException if an IO exception or a database error occurs
-	 */
-	void sendControlCommand(String command) throws SQLException {
-		// send X command
-		synchronized (server) {
-			try {
-				out.writeLine(server.getCommandTemplateHeader(0) + command + server.getCommandTemplateHeader(1));
-				String error = in.waitForPrompt();
-				if (error != null)
-					throw new SQLException(error.substring(6),
-							error.substring(0, 5));
-			} catch (SocketTimeoutException e) {
-				close(); // JDBC 4.1 semantics, abort()
-				throw new SQLException("connection timed out", "08M33");
-			} catch (IOException e) {
-				throw new SQLException(e.getMessage(), "08000");
-			}
-		}
-	}
-
-	/**
-	 * Adds a warning to the pile of warnings this Connection object
-	 * has.  If there were no warnings (or clearWarnings was called)
-	 * this warning will be the first, otherwise this warning will get
-	 * appended to the current warning.
-	 *
-	 * @param reason the warning message
-	 */
-	void addWarning(String reason, String sqlstate) {
-		if (warnings == null) {
-			warnings = new SQLWarning(reason, sqlstate);
-		} else {
-			warnings.setNextWarning(new SQLWarning(reason, sqlstate));
-		}
-	}
-
-	/** the default number of rows that are (attempted to) read at once */
-	private final static int DEF_FETCHSIZE = 250;
-	/** The sequence counter */
-	private static int seqCounter = 0;
-
-	/** An optional thread that is used for sending large queries */
-	private SendThread sendThread = null;
-
-	/**
-	 * A Response is a message sent by the server to indicate some
-	 * action has taken place, and possible results of that action.
-	 */
-	// {{{ interface Response
-	interface Response {
-		/**
-		 * Adds a line to the underlying Response implementation.
-		 * 
-		 * @param line the header line as String
-		 * @param linetype the line type according to the MAPI protocol
-		 * @return a non-null String if the line is invalid,
-		 *         or additional lines are not allowed.
-		 */
-		String addLine(String line, int linetype);
-
-		/**
-		 * Returns whether this Reponse expects more lines to be added
-		 * to it.
-		 *
-		 * @return true if a next line should be added, false otherwise
-		 */
-		boolean wantsMore();
-
-		/**
-		 * Indicates that no more header lines will be added to this
-		 * Response implementation.
-		 * 
-		 * @throws SQLException if the contents of the Response is not
-		 *         consistent or sufficient.
-		 */
-		void complete() throws SQLException;
-
-		/**
-		 * Instructs the Response implementation to close and do the
-		 * necessary clean up procedures.
-		 *
-		 */
-		void close();
-	}
-	// }}}
+    /**
+     * Creates a PreparedStatement object that will generate ResultSet
+     * objects with the given type and concurrency.  This method is the
+     * same as the prepareStatement method above, but it allows the
+     * default result set type and concurrency to be overridden.
+     *
+     * @param sql a String object that is the SQL statement to be sent to the
+     *            database; may contain one or more ? IN parameters
+     * @param resultSetType a result set type; one of
+     *        ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
+     *        or ResultSet.TYPE_SCROLL_SENSITIVE
+     * @param resultSetConcurrency a concurrency type; one of
+     *        ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
+     * @return a new PreparedStatement object containing the pre-compiled SQL
+     *         statement that will produce ResultSet objects with the given
+     *         type and concurrency
+     * @throws SQLException if a database access error occurs or the given
+     *                      parameters are not ResultSet constants indicating
+     *                      type and concurrency
+     */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+        return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
+    }
 
-	/**
-	 * The ResultSetResponse represents a tabular result sent by the
-	 * server.  This is typically an SQL table.  The MAPI headers of the
-	 * Response look like:
-	 * <pre>
-	 * &amp;1 1 28 2 10
-	 * # name,     value # name
-	 * # varchar,  varchar # type
-	 * </pre>
-	 * there the first line consists out of<br />
-	 * <tt>&amp;"qt" "id" "tc" "cc" "rc"</tt>.
-	 */
-	// {{{ ResultSetResponse class implementation
-	class ResultSetResponse implements Response {
-		/** The number of columns in this result */
-		public final int columncount;
-		/** The total number of rows this result set has */
-		public final int tuplecount;
-		/** The numbers of rows to retrieve per DataBlockResponse */
-		private int cacheSize;
-		/** The table ID of this result */
-		public final int id;
-		/** The names of the columns in this result */
-		private String[] name;
-		/** The types of the columns in this result */
-		private String[] type;
-		/** The max string length for each column in this result */
-		private int[] columnLengths;
-		/** The table for each column in this result */
-		private String[] tableNames;
-		/** The query sequence number */
-		private final int seqnr;
-		/** A List of result blocks (chunks of size fetchSize/cacheSize) */
-		private DataBlockResponse[] resultBlocks;
-
-		/** A bitmap telling whether the headers are set or not */
-		private boolean[] isSet;
-		/** Whether this Response is closed */
-		private boolean closed;
-
-		/** The Connection that we should use when requesting a new block */
-		private MonetConnection.ResponseList parent;
-		/** Whether the fetchSize was explitly set by the user */
-		private boolean cacheSizeSetExplicitly = false;
-		/** Whether we should send an Xclose command to the server
-		 *  if we close this Response */
-		private boolean destroyOnClose;
-		/** the offset to be used on Xexport queries */
-		private int blockOffset = 0;
-
-		/** A parser for header lines */
-		HeaderLineParser hlp;
-
-		private final static int NAMES	= 0;
-		private final static int TYPES	= 1;
-		private final static int TABLES	= 2;
-		private final static int LENS	= 3;
-
+    /**
+     * Creates a PreparedStatement object that will generate ResultSet
+     * objects with the given type, concurrency, and holdability.
+     *
+     * This method is the same as the prepareStatement method above, but
+     * it allows the default result set type, concurrency, and
+     * holdability to be overridden.
+     *
+     * @param sql a String object that is the SQL statement to be sent
+     * to the database; may contain one or more ? IN parameters
+     * @param resultSetType one of the following ResultSet constants:
+     * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
+     * or ResultSet.TYPE_SCROLL_SENSITIVE
+     * @param resultSetConcurrency one of the following ResultSet
+     * constants: ResultSet.CONCUR_READ_ONLY or
+     * ResultSet.CONCUR_UPDATABLE
+     * @param resultSetHoldability one of the following ResultSet
+     * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
+     * ResultSet.CLOSE_CURSORS_AT_COMMIT
+     * @return a new PreparedStatement object, containing the
+     * pre-compiled SQL statement, that will generate ResultSet objects
+     * with the given type, concurrency, and holdability
+     * @throws SQLException if a database access error occurs or the
+     * given parameters are not ResultSet constants indicating type,
+     * concurrency, and holdability
+     */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
+        try {
+            PreparedStatement ret = new MonetPreparedStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability, sql);
+            // store it in the map for when we close...
+            statements.put(ret, null);
+            return ret;
+        } catch (IllegalArgumentException e) {
+            throw new SQLException(e.toString(), "M0M03");
+        }
+        // we don't have to catch SQLException because that is declared to
+        // be thrown
+    }
 
-		/**
-		 * Sole constructor, which requires a MonetConnection parent to
-		 * be given.
-		 *
-		 * @param id the ID of the result set
-		 * @param tuplecount the total number of tuples in the result set
-		 * @param columncount the number of columns in the result set
-		 * @param rowcount the number of rows in the current block
-		 * @param parent the parent that created this Response and will
-		 *               supply new result blocks when necessary
-		 * @param seq the query sequence number
-		 */
-		ResultSetResponse(
-				int id,
-				int tuplecount,
-				int columncount,
-				int rowcount,
-				MonetConnection.ResponseList parent,
-				int seq)
-			throws SQLException
-		{
-			isSet = new boolean[7];
-			this.parent = parent;
-			if (parent.cachesize == 0) {
-				/* Below we have to calculate how many "chunks" we need
-				 * to allocate to store the entire result.  However, if
-				 * the user didn't set a cache size, as in this case, we
-				 * need to stick to our defaults. */
-				cacheSize = MonetConnection.DEF_FETCHSIZE;
-				cacheSizeSetExplicitly = false;
-			} else {
-				cacheSize = parent.cachesize;
-				cacheSizeSetExplicitly = true;
-			}
-			/* So far, so good.  Now the problem with EXPLAIN, DOT, etc
-			 * queries is, that they don't support any block fetching,
-			 * so we need to always fetch everything at once.  For that
-			 * reason, the cache size is here set to the rowcount if
-			 * it's larger, such that we do a full fetch at once.
-			 * (Because we always set a reply_size, we can only get a
-			 * larger rowcount from the server if it doesn't paginate,
-			 * because it's a pseudo SQL result.) */
-			if (rowcount > cacheSize)
-				cacheSize = rowcount;
-			seqnr = seq;
-			closed = false;
-			destroyOnClose = id > 0 && tuplecount > rowcount;
-
-			this.id = id;
-			this.tuplecount = tuplecount;
-			this.columncount = columncount;
-			this.resultBlocks =
-				new DataBlockResponse[(tuplecount / cacheSize) + 1];
-
-			hlp = server.getHeaderLineParser(columncount);
+    /**
+     * Creates a default PreparedStatement object that has the
+     * capability to retrieve auto-generated keys.  The given constant
+     * tells the driver whether it should make auto-generated keys
+     * available for retrieval.  This parameter is ignored if the SQL
+     * statement is not an INSERT statement.
+     *
+     * Note: This method is optimized for handling parametric SQL
+     * statements that benefit from precompilation.  If the driver
+     * supports precompilation, the method prepareStatement will send
+     * the statement to the database for precompilation. Some drivers
+     * may not support precompilation.  In this case, the statement may
+     * not be sent to the database until the PreparedStatement object is
+     * executed.  This has no direct effect on users; however, it does
+     * affect which methods throw certain SQLExceptions.
+     *
+     * Result sets created using the returned PreparedStatement object
+     * will by default be type TYPE_FORWARD_ONLY and have a concurrency
+     * level of CONCUR_READ_ONLY.
+     *
+     * @param sql an SQL statement that may contain one or more '?' IN
+     *        parameter placeholders
+     * @param autoGeneratedKeys a flag indicating whether auto-generated
+     *        keys should be returned; one of
+     *        Statement.RETURN_GENERATED_KEYS or
+     *        Statement.NO_GENERATED_KEYS
+     * @return a new PreparedStatement object, containing the
+     *         pre-compiled SQL statement, that will have the capability
+     *         of returning auto-generated keys
+     * @throws SQLException - if a database access error occurs or the
+     *         given parameter is not a Statement  constant indicating
+     *         whether auto-generated keys should be returned
+     */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+        if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS && autoGeneratedKeys != Statement.NO_GENERATED_KEYS) {
+            throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05");
+        }
+		/* MonetDB has no way to disable this, so just do the normal thing ;) */
+        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+    }
 
-			resultBlocks[0] = new DataBlockResponse(
-				rowcount,
-				parent.rstype == ResultSet.TYPE_FORWARD_ONLY
-			);
-		}
-
-		/**
-		 * Parses the given string and changes the value of the matching
-		 * header appropriately, or passes it on to the underlying
-		 * DataResponse.
-		 *
-		 * @param tmpLine the string that contains the header
-		 * @return a non-null String if the header cannot be parsed or
-		 *         is unknown
-		 */
-		// {{{ addLine
-		@Override
-		public String addLine(String tmpLine, int linetype) {
-			if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) {
-				return resultBlocks[0].addLine(tmpLine, linetype);
-			}
-
-			if (linetype != AbstractMCLReader.HEADER)
-				return "header expected, got: " + tmpLine;
+    @Override
+    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {return null;}
 
-			// depending on the name of the header, we continue
-			try {
-				switch (hlp.parse(tmpLine)) {
-					case HeaderLineParser.NAME:
-						name = hlp.values.clone();
-						isSet[NAMES] = true;
-					break;
-					case HeaderLineParser.LENGTH:
-						columnLengths = hlp.intValues.clone();
-						isSet[LENS] = true;
-					break;
-					case HeaderLineParser.TYPE:
-						type = hlp.values.clone();
-						isSet[TYPES] = true;
-					break;
-					case HeaderLineParser.TABLE:
-						tableNames = hlp.values.clone();
-						isSet[TABLES] = true;
-					break;
-				}
-			} catch (MCLParseException e) {
-				return e.getMessage();
-			}
-
-			// all is well
-			return null;
-		}
-		// }}}
-
-		/**
-		 * Returns whether this ResultSetResponse needs more lines.
-		 * This method returns true if not all headers are set, or the
-		 * first DataBlockResponse reports to want more.
-		 */
-		@Override
-		public boolean wantsMore() {
-			return !(isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) || resultBlocks[0].wantsMore();
-		}
+    @Override
+    public PreparedStatement prepareStatement(String sql, String[] columnNames) {return null;}
 
-		/**
-		 * Returns an array of Strings containing the values between
-		 * ',\t' separators.
-		 *
-		 * @param chrLine a character array holding the input data
-		 * @param start where the relevant data starts
-		 * @param stop where the relevant data stops
-		 * @return an array of Strings
-		 */
-		final private String[] getValues(char[] chrLine, int start, int stop) {
-			int elem = 0;
-			String[] values = new String[columncount];
-			
-			for (int i = start; i < stop; i++) {
-				if (chrLine[i] == '\t' && chrLine[i - 1] == ',') {
-					values[elem++] =
-						new String(chrLine, start, i - 1 - start);
-					start = i + 1;
-				}
-			}
-			// at the left over part
-			values[elem++] = new String(chrLine, start, stop - start);
-
-			return values;
-		}
-
-		/**
-		 * Adds the given DataBlockResponse to this ResultSetResponse at
-		 * the given block position.
-		 *
-		 * @param offset the offset number of rows for this block
-		 * @param rr the DataBlockResponse to add
-		 */
-		void addDataBlockResponse(int offset, DataBlockResponse rr) {
-			int block = (offset - blockOffset) / cacheSize;
-			resultBlocks[block] = rr;
-		}
-
-		/**
-		 * Marks this Response as being completed.  A complete Response
-		 * needs to be consistent with regard to its internal data.
-		 *
-		 * @throws SQLException if the data currently in this Response is not
-		 *                      sufficient to be consistant
-		 */
-		@Override
-		public void complete() throws SQLException {
-			String error = "";
-			if (!isSet[NAMES]) error += "name header missing\n";
-			if (!isSet[TYPES]) error += "type header missing\n";
-			if (!isSet[TABLES]) error += "table name header missing\n";
-			if (!isSet[LENS]) error += "column width header missing\n";
-			if (!error.equals("")) throw new SQLException(error, "M0M10");
-		}
-
-		/**
-		 * Returns the names of the columns
-		 *
-		 * @return the names of the columns
-		 */
-		String[] getNames() {
-			return name;
-		}
-
-		/**
-		 * Returns the types of the columns
-		 *
-		 * @return the types of the columns
-		 */
-		String[] getTypes() {
-			return type;
-		}
+    /**
+     * Removes the given Savepoint object from the current transaction.
+     * Any reference to the savepoint after it have been removed will
+     * cause an SQLException to be thrown.
+     *
+     * @param savepoint the Savepoint object to be removed
+     * @throws SQLException if a database access error occurs or the given
+     *         Savepoint object is not a valid savepoint in the current
+     *         transaction
+     */
+    @Override
+    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+        if (!(savepoint instanceof MonetSavepoint)) {
+            throw new SQLException("This driver can only handle savepoints it created itself", "M0M06");
+        }
+        MonetSavepoint sp = (MonetSavepoint) savepoint;
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send the appropriate query string to the database
+        try {
+            l.processQuery("RELEASE SAVEPOINT " + sp.getName());
+        } finally {
+            l.close();
+        }
+    }
 
-		/**
-		 * Returns the tables of the columns
-		 *
-		 * @return the tables of the columns
-		 */
-		String[] getTableNames() {
-			return tableNames;
-		}
-
-		/**
-		 * Returns the lengths of the columns
-		 *
-		 * @return the lengths of the columns
-		 */
-		int[] getColumnLengths() {
-			return columnLengths;
-		}
-
-		/**
-		 * Returns the cache size used within this Response
-		 *
-		 * @return the cache size
-		 */
-		int getCacheSize() {
-			return cacheSize;
-		}
-
-		/**
-		 * Returns the current block offset
-		 *
-		 * @return the current block offset
-		 */
-		int getBlockOffset() {
-			return blockOffset;
-		}
+    /**
+     * Undoes all changes made in the current transaction and releases
+     * any database locks currently held by this Connection object. This
+     * method should be used only when auto-commit mode has been
+     * disabled.
+     *
+     * @throws SQLException if a database access error occurs or this
+     *         Connection object is in auto-commit mode
+     * @see #setAutoCommit(boolean)
+     */
+    @Override
+    public void rollback() throws SQLException {
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send rollback to the server
+        try {
+            l.processQuery("ROLLBACK");
+        } finally {
+            l.close();
+        }
+    }
 
-		/**
-		 * Returns the ResultSet type, FORWARD_ONLY or not.
-		 *
-		 * @return the ResultSet type
-		 */
-		int getRSType() {
-			return parent.rstype;
-		}
-
-		/**
-		 * Returns the concurrency of the ResultSet.
-		 *
-		 * @return the ResultSet concurrency
-		 */
-		int getRSConcur() {
-			return parent.rsconcur;
-		}
+    /**
+     * Undoes all changes made after the given Savepoint object was set.
+     *
+     * This method should be used only when auto-commit has been
+     * disabled.
+     *
+     * @param savepoint the Savepoint object to roll back to
+     * @throws SQLException if a database access error occurs, the
+     *         Savepoint object is no longer valid, or this Connection
+     *         object is currently in auto-commit mode
+     */
+    @Override
+    public void rollback(Savepoint savepoint) throws SQLException {
+        if (!(savepoint instanceof MonetSavepoint)) {
+            throw new SQLException("This driver can only handle savepoints it created itself", "M0M06");
+        }
 
-		/**
-		 * Returns a line from the cache. If the line is already present in the
-		 * cache, it is returned, if not apropriate actions are taken to make
-		 * sure the right block is being fetched and as soon as the requested
-		 * line is fetched it is returned.
-		 *
-		 * @param row the row in the result set to return
-		 * @return the exact row read as requested or null if the requested row
-		 *         is out of the scope of the result set
-		 * @throws SQLException if an database error occurs
-		 */
-		String getLine(int row) throws SQLException {
-			if (row >= tuplecount || row < 0)
-				return null;
-
-			int block = (row - blockOffset) / cacheSize;
-			int blockLine = (row - blockOffset) % cacheSize;
-
-			// do we have the right block loaded? (optimistic try)
-			DataBlockResponse rawr;
-			// load block if appropriate
-			if ((rawr = resultBlocks[block]) == null) {
-				/// TODO: ponder about a maximum number of blocks to keep
-				///       in memory when dealing with random access to
-				///       reduce memory blow-up
-
-				// if we're running forward only, we can discard the oldmapi
-				// block loaded
-				if (parent.rstype == ResultSet.TYPE_FORWARD_ONLY) {
-					for (int i = 0; i < block; i++)
-						resultBlocks[i] = null;
+        MonetSavepoint sp = (MonetSavepoint)savepoint;
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send the appropriate query string to the database
+        try {
+            l.processQuery("ROLLBACK TO SAVEPOINT " + sp.getName());
+        } finally {
+            l.close();
+        }
+    }
 
-					if (MonetConnection.seqCounter - 1 == seqnr &&
-							!cacheSizeSetExplicitly &&
-							tuplecount - row > cacheSize &&
-							cacheSize < MonetConnection.DEF_FETCHSIZE * 10)
-					{
-						// there has no query been issued after this
-						// one, so we can consider this an uninterrupted
-						// continuation request.  Let's once increase
-						// the cacheSize as it was not explicitly set,
-						// since the chances are high that we won't
-						// bother anyone else by doing so, and just
-						// gaining some performance.
-
-						// store the previous position in the
-						// blockOffset variable
-						blockOffset += cacheSize;
-
-						// increase the cache size (a lot)
-						cacheSize *= 10;
-
-						// by changing the cacheSize, we also
-						// change the block measures.  Luckily
-						// we don't care about previous blocks
-						// because we have a forward running
-						// pointer only.  However, we do have
-						// to recalculate the block number, to
-						// ensure the next call to find this
-						// new block.
-						block = (row - blockOffset) / cacheSize;
-						blockLine = (row - blockOffset) % cacheSize;
-					}
-				}
-
-				// ok, need to fetch cache block first
-				parent.executeQuery(
-						server.getCommandHeaderTemplates(),
-						"export " + id + " " + ((block * cacheSize) + blockOffset) + " " + cacheSize 
-				);
-				rawr = resultBlocks[block];
-				if (rawr == null) throw
-					new AssertionError("block " + block + " should have been fetched by now :(");
-			}
-
-			return rawr.getRow(blockLine);
-		}
-
-		/**
-		 * Closes this Response by sending an Xclose to the server indicating
-		 * that the result can be closed at the server side as well.
-		 */
-		@Override
-		public void close() {
-			if (closed) return;
-			// send command to server indicating we're done with this
-			// result only if we had an ID in the header and this result
-			// was larger than the reply size
-			try {
-				if (destroyOnClose) sendControlCommand("close " + id);
-			} catch (SQLException e) {
-				// probably a connection error...
-			}
-
-			// close the data block associated with us
-			for (int i = 1; i < resultBlocks.length; i++) {
-				DataBlockResponse r = resultBlocks[i];
-				if (r != null) r.close();
-			}
-
-			closed = true;
-		}
+    /**
+     * Sets this connection's auto-commit mode to the given state. If a
+     * connection is in auto-commit mode, then all its SQL statements
+     * will be executed and committed as individual transactions.
+     * Otherwise, its SQL statements are grouped into transactions that
+     * are terminated by a call to either the method commit or the
+     * method rollback. By default, new connections are in auto-commit
+     * mode.
+     *
+     * The commit occurs when the statement completes or the next
+     * execute occurs, whichever comes first. In the case of statements
+     * returning a ResultSet object, the statement completes when the
+     * last row of the ResultSet object has been retrieved or the
+     * ResultSet object has been closed. In advanced cases, a single
+     * statement may return multiple results as well as output parameter
+     * values. In these cases, the commit occurs when all results and
+     * output parameter values have been retrieved.
+     *
+     * NOTE: If this method is called during a transaction, the
+     * transaction is committed.
+     *
+     * @param autoCommit true to enable auto-commit mode; false to disable it
+     * @throws SQLException if a database access error occurs
+     * @see #getAutoCommit()
+     */
+    @Override
+    public void setAutoCommit(boolean autoCommit) throws SQLException {
+        if (this.autoCommit != autoCommit) {
+            sendControlCommand("auto_commit " + (autoCommit ? "1" : "0"));
+            this.autoCommit = autoCommit;
+        }
+    }
 
-		/**
-		 * Returns whether this Response is closed
-		 *
-		 * @return whether this Response is closed
-		 */
-		boolean isClosed() {
-			return closed;
-		}
-	}
-	// }}}
-	
-	/**
-	 * The DataBlockResponse is tabular data belonging to a
-	 * ResultSetResponse.  Tabular data from the server typically looks
-	 * like:
-	 * <pre>
-	 * [ "value",	56	]
-	 * </pre>
-	 * where each column is separated by ",\t" and each tuple surrounded
-	 * by brackets ("[" and "]").  A DataBlockResponse object holds the
-	 * raw data as read from the server, in a parsed manner, ready for
-	 * easy retrieval.
-	 * 
-	 * This object is not intended to be queried by multiple threads
-	 * synchronously. It is designed to work for one thread retrieving
-	 * rows from it.  When multiple threads will retrieve rows from this
-	 * object, it is possible for threads to get the same data.
-	 */
-	// {{{ DataBlockResponse class implementation
-	static class DataBlockResponse implements Response {
-		/** The String array to keep the data in */
-		private final String[] data;
+    /**
+     * Sets the given catalog name in order to select a subspace of this
+     * Connection object's database in which to work.  If the driver
+     * does not support catalogs, it will silently ignore this request.
+     */
+    @Override
+    public void setCatalog(String catalog) throws SQLException {
+        // silently ignore this request as MonetDB does not support catalogs
+    }
 
-		/** The counter which keeps the current position in the data array */
-		private int pos;
-		/** Whether we can discard lines as soon as we have read them */
-		private boolean forwardOnly;
-
-		/**
-		 * Constructs a DataBlockResponse object
-		 * @param size the size of the data array to create
-		 * @param forward whether this is a forward only result
-		 */
-		DataBlockResponse(int size, boolean forward) {
-			pos = -1;
-			data = new String[size];
-			forwardOnly = forward;
-		}
+    /**
+     * Not implemented by MonetDB's JDBC driver.
+     *
+     * @param name The name of the client info property to set
+     * @param value The value to set the client info property to. If the
+     *        value is null, the current value of the specified property
+     *        is cleared.
+     */
+    @Override
+    public void setClientInfo(String name, String value) {
+        addWarning("clientInfo: " + name + "is not a recognised property", "01M07");
+    }
 
-		/**
-		 * addLine adds a String of data to this object's data array.
-		 * Note that an IndexOutOfBoundsException can be thrown when an
-		 * attempt is made to add more than the original construction size
-		 * specified.
-		 * 
-		 * @param line the header line as String
-		 * @param linetype the line type according to the MAPI protocol
-		 * @return a non-null String if the line is invalid,
-		 *         or additional lines are not allowed.
-		 */
-		@Override
-		public String addLine(String line, int linetype) {
-			if (linetype != AbstractMCLReader.RESULT)
-				return "protocol violation: unexpected line in data block: " + line;
-			// add to the backing array
-			data[++pos] = line;
-
-			// all is well
-			return null;
-		}
+    /**
+     * Not implemented by MonetDB's JDBC driver.
+     *
+     * @param props The list of client info properties to set
+     */
+    @Override
+    public void setClientInfo(Properties props) {
+        for (Map.Entry<Object, Object> entry : props.entrySet()) {
+            setClientInfo(entry.getKey().toString(), entry.getValue().toString());
+        }
+    }
 
-		/**
-		 * Returns whether this Reponse expects more lines to be added
-		 * to it.
-		 *
-		 * @return true if a next line should be added, false otherwise
-		 */
-		@Override
-		public boolean wantsMore() {
-			// remember: pos is the value already stored
-			return pos + 1 < data.length;
-		}
-
-		/**
-		 * Indicates that no more header lines will be added to this
-		 * Response implementation.  In most cases this is a redundant
-		 * operation because the data array is full.  However... it can
-		 * happen that this is NOT the case!
-		 *
-		 * @throws SQLException if not all rows are filled
-		 */
-		@Override
-		public void complete() throws SQLException {
-			if ((pos + 1) != data.length) throw
-				new SQLException("Inconsistent state detected!  Current block capacity: " + data.length + ", block usage: " + (pos + 1) + ".  Did MonetDB send what it promised to?", "M0M10");
-		}
-
-		/**
-		 * Instructs the Response implementation to close and do the
-		 * necessary clean up procedures.
-		 *
-		 */
-		@Override
-		public void close() {
-			// feed all rows to the garbage collector
-			for (int i = 0; i < data.length; i++) data[i] = null;
-		}
+    /**
+     * Changes the default holdability of ResultSet objects created using this
+     * Connection object to the given holdability. The default holdability of
+     * ResultSet objects can be be determined by invoking DatabaseMetaData.getResultSetHoldability().
+     *
+     * @param holdability - a ResultSet holdability constant; one of
+     *	ResultSet.HOLD_CURSORS_OVER_COMMIT or
+     *	ResultSet.CLOSE_CURSORS_AT_COMMIT
+     * @see #getHoldability()
+     */
+    @Override
+    public void setHoldability(int holdability) throws SQLException {
+        // we only support ResultSet.HOLD_CURSORS_OVER_COMMIT
+        if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT)
+            throw new SQLFeatureNotSupportedException("setHoldability(CLOSE_CURSORS_AT_COMMIT) not supported", "0A000");
+    }
 
-		/**
-		 * Retrieves the required row.  Warning: if the requested rows
-		 * is out of bounds, an IndexOutOfBoundsException will be
-		 * thrown.
-		 *
-		 * @param line the row to retrieve
-		 * @return the requested row as String
-		 */
-		String getRow(int line) {
-			if (forwardOnly) {
-				String ret = data[line];
-				data[line] = null;
-				return ret;
-			} else {
-				return data[line];
-			}
-		}
-	}
-	// }}}
-
-	/**
-	 * The UpdateResponse represents an update statement response.  It
-	 * is issued on an UPDATE, INSERT or DELETE SQL statement.  This
-	 * response keeps a count field that represents the affected rows
-	 * and a field that contains the last inserted auto-generated ID, or
-	 * -1 if not applicable.<br />
-	 * <tt>&amp;2 0 -1</tt>
-	 */
-	// {{{ UpdateResponse class implementation
-	static class UpdateResponse implements Response {
-		public final int count;
-		public final String lastid;
-		
-		public UpdateResponse(int cnt, String id) {
-			// fill the blank finals
-			this.count = cnt;
-			this.lastid = id;
-		}
-
-		@Override
-		public String addLine(String line, int linetype) {
-			return "Header lines are not supported for an UpdateResponse";
-		}
-
-		@Override
-		public boolean wantsMore() {
-			return false;
-		}
-
-		@Override
-		public void complete() {
-			// empty, because there is nothing to check
-		}
-
-		@Override
-		public void close() {
-			// nothing to do here...
-		}
-	}
-	// }}}
+    /**
+     * Puts this connection in read-only mode as a hint to the driver to
+     * enable database optimizations.  MonetDB doesn't support any mode
+     * here, hence an SQLWarning is generated if attempted to set
+     * to true here.
+     *
+     * @param readOnly true enables read-only mode; false disables it
+     * @throws SQLException if a database access error occurs or this
+     *         method is called during a transaction.
+     */
+    @Override
+    public void setReadOnly(boolean readOnly) throws SQLException {
+        if (readOnly) {
+            addWarning("cannot setReadOnly(true): read-only Connection mode not supported", "01M08");
+        }
+    }
 
-	/**
-	 * The SchemaResponse represents an schema modification response.
-	 * It is issued on statements like CREATE, DROP or ALTER TABLE.
-	 * This response keeps a field that represents the success state, as
-	 * defined by JDBC, which is currently in MonetDB's case alwats
-	 * SUCCESS_NO_INFO.  Note that this state is not sent by the
-	 * server.<br />
-	 * <tt>&amp;3</tt>
-	 */
-	// {{{ SchemaResponse class implementation
-	class SchemaResponse implements Response {
-		public final int state = Statement.SUCCESS_NO_INFO;
-		
-		@Override
-		public String addLine(String line, int linetype) {
-			return "Header lines are not supported for a SchemaResponse";
-		}
-
-		@Override
-		public boolean wantsMore() {
-			return false;
-		}
-
-		@Override
-		public void complete() {
-			// empty, because there is nothing to check
-		}
-
-		@Override
-		public void close() {
-			// nothing to do here...
-		}
-	}
-	// }}}
-
-	/**
-	 * The AutoCommitResponse represents a transaction message.  It
-	 * stores (a change in) the server side auto commit mode.<br />
-	 * <tt>&amp;4 (t|f)</tt>
-	 */
-	// {{{ AutoCommitResponse class implementation
-	class AutoCommitResponse extends SchemaResponse {
-		public final boolean autocommit;
-		
-		public AutoCommitResponse(boolean ac) {
-			// fill the blank final
-			this.autocommit = ac;
-		}
-	}
-	// }}}
-
-	/**
-	 * A list of Response objects.  Responses are added to this list.
-	 * Methods of this class are not synchronized.  This is left as
-	 * responsibility to the caller to prevent concurrent access.
-	 */
-	// {{{ ResponseList class implementation
-	class ResponseList {
-		/** The cache size (number of rows in a DataBlockResponse object) */
-		final int cachesize;
-		/** The maximum number of results for this query */
-		final int maxrows;
-		/** The ResultSet type to produce */
-		final int rstype;
-		/** The ResultSet concurrency to produce */
-		final int rsconcur;
-		/** The sequence number of this ResponseList */
-		final int seqnr;
-		/** A list of the Responses associated with the query,
-		 *  in the right order */
-		private List<Response> responses;
-		/** A map of ResultSetResponses, used for additional
-		 *  DataBlockResponse mapping */
-		private Map<Integer, ResultSetResponse> rsresponses;
-
-		/** The current header returned by getNextResponse() */
-		private int curResponse;
+    /**
+     * Creates an unnamed savepoint in the current transaction and
+     * returns the new Savepoint object that represents it.
+     *
+     * @return the new Savepoint object
+     * @throws SQLException if a database access error occurs or this Connection
+     *         object is currently in auto-commit mode
+     */
+    @Override
+    public Savepoint setSavepoint() throws SQLException {
+        // create a new Savepoint object
+        MonetSavepoint sp = new MonetSavepoint();
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send the appropriate query string to the database
+        try {
+            l.processQuery("SAVEPOINT " + sp.getName());
+        } finally {
+            l.close();
+        }
+        return sp;
+    }
 
-		/**
-		 * Main constructor.  The query argument can either be a String
-		 * or List.  An SQLException is thrown if another object
-		 * instance is supplied.
-		 *
-		 * @param cachesize overall cachesize to use
-		 * @param maxrows maximum number of rows to allow in the set
-		 * @param rstype the type of result sets to produce
-		 * @param rsconcur the concurrency of result sets to produce
-		 */
-		ResponseList(
-				int cachesize,
-				int maxrows,
-				int rstype,
-				int rsconcur
-		) throws SQLException {
-			this.cachesize = cachesize;
-			this.maxrows = maxrows;
-			this.rstype = rstype;
-			this.rsconcur = rsconcur;
-			responses = new ArrayList<>();
-			curResponse = -1;
-			seqnr = MonetConnection.seqCounter++;
-		}
-
-		/**
-		 * Retrieves the next available response, or null if there are
-		 * no more responses.
-		 *
-		 * @return the next Response available or null
-		 */
-		Response getNextResponse() throws SQLException {
-			if (rstype == ResultSet.TYPE_FORWARD_ONLY) {
-				// free resources if we're running forward only
-				if (curResponse >= 0 && curResponse < responses.size()) {
-					Response tmp = responses.get(curResponse);
-					if (tmp != null) tmp.close();
-					responses.set(curResponse, null);
-				}
-			}
-			curResponse++;
-			if (curResponse >= responses.size()) {
-				// ResponseList is obviously completed so, there are no
-				// more responses
-				return null;
-			} else {
-				// return this response
-				return responses.get(curResponse);
-			}
-		}
-
-		/**
-		 * Closes the Reponse at index i, if not null.
-		 *
-		 * @param i the index position of the header to close
-		 */
-		void closeResponse(int i) {
-			if (i < 0 || i >= responses.size()) return;
-			Response tmp = responses.set(i, null);
-			if (tmp != null)
-				tmp.close();
-		}
-
-		/**
-		 * Closes the current response.
-		 */
-		void closeCurrentResponse() {
-			closeResponse(curResponse);
-		}
-
-		/**
-		 * Closes the current and previous responses.
-		 */
-		void closeCurOldResponses() {
-			for (int i = curResponse; i >= 0; i--) {
-				closeResponse(i);
-			}
-		}
+    /**
+     * Creates a savepoint with the given name in the current
+     * transaction and returns the new Savepoint object that represents
+     * it.
+     *
+     * @param name a String containing the name of the savepoint
+     * @return the new Savepoint object
+     * @throws SQLException if a database access error occurs or this Connection
+     *         object is currently in auto-commit mode
+     */
+    @Override
+    public Savepoint setSavepoint(String name) throws SQLException {
+        // create a new Savepoint object
+        MonetSavepoint sp;
+        try {
+            sp = new MonetSavepoint(name);
+        } catch (IllegalArgumentException e) {
+            throw new SQLException(e.getMessage(), "M0M03");
+        }
+        // note: can't use sendIndependentCommand here because we need
+        // to process the auto_commit state the server gives
+        // create a container for the result
+        ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
+        // send the appropriate query string to the database
+        try {
+            l.processQuery("SAVEPOINT " + sp.getName());
+        } finally {
+            l.close();
+        }
+        return sp;
+    }
 
-		/**
-		 * Closes this ResponseList by closing all the Responses in this
-		 * ResponseList.
-		 */
-		void close() {
-			for (int i = 0; i < responses.size(); i++) {
-				closeResponse(i);
-			}
-		}
-
-		/**
-		 * Returns whether this ResponseList has still unclosed
-		 * Responses.
-		 */
-		boolean hasUnclosedResponses() {
-			for (Response r : responses) {
-				if (r != null)
-					return true;
-			}
-			return false;
-		}
-
-		/**
-		 * Executes the query contained in this ResponseList, and
-		 * stores the Responses resulting from this query in this
-		 * ResponseList.
-		 *
-		 * @throws SQLException if a database error occurs
-		 */
-		void processQuery(String query) throws SQLException {
-			executeQuery(server.getQueryHeaderTemplates(), query);
-		}
-
-		/**
-		 * Internal executor of queries.
-		 *
-		 * @param templ the template to fill in
-		 * @param query the query to execute
-		 * @throws SQLException if a database error occurs
-		 */
-		@SuppressWarnings("fallthrough")
-		void executeQuery(String[] templ, String query)
-			throws SQLException
-		{
-			boolean sendThreadInUse = false;
-			String error = null;
+    /**
+     * Attempts to change the transaction isolation level for this
+     * Connection object to the one given.  The constants defined in the
+     * interface Connection are the possible transaction isolation
+     * levels.
+     *
+     * @param level one of the following Connection constants:
+     *        Connection.TRANSACTION_READ_UNCOMMITTED,
+     *        Connection.TRANSACTION_READ_COMMITTED,
+     *        Connection.TRANSACTION_REPEATABLE_READ, or
+     *        Connection.TRANSACTION_SERIALIZABLE.
+     */
+    @Override
+    public void setTransactionIsolation(int level) {
+        if (level != TRANSACTION_SERIALIZABLE) {
+            addWarning("MonetDB only supports fully serializable " + "transactions, continuing with transaction level " +
+                    "raised to TRANSACTION_SERIALIZABLE", "01M09");
+        }
+    }
 
-			try {
-				synchronized (server) {
-					// make sure we're ready to send query; read data till we
-					// have the prompt it is possible (and most likely) that we
-					// already have the prompt and do not have to skip any
-					// lines.  Ignore errors from previous result sets.
-					in.waitForPrompt();
-
-					// {{{ set reply size
-					/**
-					 * Change the reply size of the server.  If the given
-					 * value is the same as the current value known to use,
-					 * then ignore this call.  If it is set to 0 we get a
-					 * prompt after the server sent it's header.
-					 */
-					int size = cachesize == 0 ? DEF_FETCHSIZE : cachesize;
-					size = maxrows != 0 ? Math.min(maxrows, size) : size;
-					// don't do work if it's not needed
-					if (server.getLang() == AbstractMonetDBConnection.LANG_SQL && size != curReplySize && templ != server.getCommandHeaderTemplates()) {
-						sendControlCommand("reply_size " + size);
+    /**
+     * Installs the given TypeMap object as the type map for this
+     * Connection object. The type map will be used for the custom
+     * mapping of SQL structured types and distinct types.
+     *
+     * @param map the java.util.Map object to install as the replacement for
+     *        this Connection  object's default type map
+     */
+    @Override
+    public void setTypeMap(Map<String, Class<?>> map) {
+        typeMap = map;
+    }
 
-						// store the reply size after a successful change
-						curReplySize = size;
-					}
-					// }}} set reply size
+    /**
+     * Returns a string identifying this Connection to the MonetDB
+     * server.
+     *
+     * @return a String representing this Object
+     */
+    @Override
+    public String toString() {
+        return "MonetDB Connection (" + this.getJDBCURL() + ") " + (closed ? "connected" : "disconnected");
+    }
+
+    //== 1.7 methods (JDBC 4.1)
 
-					// If the query is larger than the TCP buffer size, use a
-					// special send thread to avoid deadlock with the server due
-					// to blocking behaviour when the buffer is full.  Because
-					// the server will be writing back results to us, it will
-					// eventually block as well when its TCP buffer gets full,
-					// as we are blocking an not consuming from it.  The result
-					// is a state where both client and server want to write,
-					// but block.
-					if (query.length() > server.getBlockSize()) {
-						// get a reference to the send thread
-						if (sendThread == null)
-							sendThread = new SendThread(out);
-						// tell it to do some work!
-						sendThread.runQuery(templ, query);
-						sendThreadInUse = true;
-					} else {
-						// this is a simple call, which is a lot cheaper and will
-						// always succeed for small queries.
-						out.writeLine((templ[0] == null ? "" : templ[0] + query + templ[1] == null ? "" : templ[1]));
-					}
+    /**
+     * Sets the given schema name to access.
+     *
+     * @param schema the name of a schema in which to work
+     * @throws SQLException if a database access error occurs or this
+     *         method is called on a closed connection
+     */
+    @Override
+    public void setSchema(String schema) throws SQLException {
+        if (closed) {
+            throw new SQLException("Cannot call on closed Connection", "M1M20");
+        }
+        if (schema != null) {
+            createStatement().execute("SET SCHEMA \"" + schema + "\"");
+        }
+    }
 
-					// go for new results
-					String tmpLine = in.readLine();
-					int linetype = in.getLineType();
-					Response res = null;
-					while (linetype != AbstractMCLReader.PROMPT) {
-						// each response should start with a start of header
-						// (or error)
-						switch (linetype) {
-							case AbstractMCLReader.SOHEADER:
-								// make the response object, and fill it
-								try {
-									switch (sohp.parse(tmpLine)) {
-										case StartOfHeaderParser.Q_PARSE:
-											throw new MCLParseException("Q_PARSE header not allowed here", 1);
-										case StartOfHeaderParser.Q_TABLE:
-										case StartOfHeaderParser.Q_PREPARE: {
-											int id = sohp.getNextAsInt();
-											int tuplecount = sohp.getNextAsInt();
-											int columncount = sohp.getNextAsInt();
-											int rowcount = sohp.getNextAsInt();
-											// enforce the maxrows setting
-											if (maxrows != 0 && tuplecount > maxrows)
-												tuplecount = maxrows;
-											res = new ResultSetResponse(
-													id,
-													tuplecount,
-													columncount,
-													rowcount,
-													this,
-													seqnr
-											);
-											// only add this resultset to
-											// the hashmap if it can possibly
-											// have an additional datablock
-											if (rowcount < tuplecount) {
-												if (rsresponses == null)
-													rsresponses = new HashMap<>();
-												rsresponses.put(
-														id,
-														(ResultSetResponse) res
-												);
-											}
-										} break;
-										case StartOfHeaderParser.Q_UPDATE:
-											res = new UpdateResponse(
-													sohp.getNextAsInt(),   // count
-													sohp.getNextAsString() // key-id
-													);
-										break;
-										case StartOfHeaderParser.Q_SCHEMA:
-											res = new SchemaResponse();
-										break;
-										case StartOfHeaderParser.Q_TRANS:
-											boolean ac = sohp.getNextAsString().equals("t");
-											if (autoCommit && ac) {
-												addWarning("Server enabled auto commit " +
-														"mode while local state " +
-														"already was auto commit.", "01M11"
-														);
-											}
-											autoCommit = ac;
-											res = new AutoCommitResponse(ac);
-										break;
-										case StartOfHeaderParser.Q_BLOCK: {
-											// a new block of results for a
-											// response...
-											int id = sohp.getNextAsInt(); 
-											sohp.getNextAsInt();	// columncount
-											int rowcount = sohp.getNextAsInt();
-											int offset = sohp.getNextAsInt();
-											ResultSetResponse t =
-												rsresponses.get(id);
-											if (t == null) {
-												error = "M0M12!no ResultSetResponse with id " + id + " found";
-												break;
-											}
-
-											DataBlockResponse r =
-												new DataBlockResponse(
-													rowcount,	// rowcount
-													t.getRSType() == ResultSet.TYPE_FORWARD_ONLY
-												);
+    /**
+     * Retrieves this Connection object's current schema name.
+     *
+     * @return the current schema name or null if there is none
+     * @throws SQLException if a database access error occurs or this
+     *         method is called on a closed connection
+     */
+    @Override
+    public String getSchema() throws SQLException {
+        if (closed) {
+            throw new SQLException("Cannot call on closed Connection", "M1M20");
+        }
+        String cur_schema;
+        Statement st = createStatement();
+        ResultSet rs = null;
+        try {
+            rs = st.executeQuery("SELECT CURRENT_SCHEMA");
+            if (!rs.next())
+                throw new SQLException("Row expected", "02000");
+            cur_schema = rs.getString(1);
+        } finally {
+            if (rs != null)
+                rs.close();
+            st.close();
+        }
+        return cur_schema;
+    }
 
-											t.addDataBlockResponse(offset, r);
-											res = r;
-										} break;
-									}
-								} catch (MCLParseException e) {
-									error = "M0M10!error while parsing start of header:\n" +
-										e.getMessage() +
-										" found: '" + tmpLine.charAt(e.getErrorOffset()) + "'" +
-										" in: \"" + tmpLine + "\"" +
-										" at pos: " + e.getErrorOffset();
-									// flush all the rest
-									in.waitForPrompt();
-									linetype = in.getLineType();
-									break;
-								}
-
-								// immediately handle errors after parsing
-								// the header (res may be null)
-								if (error != null) {
-									in.waitForPrompt();
-									linetype = in.getLineType();
-									break;
-								}
+    /**
+     * Terminates an open connection. Calling abort results in:
+     *  * The connection marked as closed
+     *  * Closes any physical connection to the database
+     *  * Releases resources used by the connection
+     *  * Insures that any thread that is currently accessing the
+     *    connection will either progress to completion or throw an
+     *    SQLException.
+     * Calling abort marks the connection closed and releases any
+     * resources. Calling abort on a closed connection is a no-op.
+     *
+     * @param executor The Executor implementation which will be used by
+     *        abort
+     * @throws SQLException if a database access error occurs or the
+     *         executor is null
+     * @throws SecurityException if a security manager exists and
+     *         its checkPermission method denies calling abort
+     */
+    @Override
+    public void abort(Executor executor) throws SQLException {
+        if (closed)
+            return;
+        if (executor == null)
+            throw new SQLException("executor is null", "M1M05");
+        // this is really the simplest thing to do, it destroys
+        // everything (in particular the server connection)
+        close();
+    }
 
-								// here we have a res object, which
-								// we can start filling
-								while (res.wantsMore()) {
-									error = res.addLine(
-											in.readLine(),
-											in.getLineType()
-									);
-									if (error != null) {
-										// right, some protocol violation,
-										// skip the rest of the result
-										error = "M0M10!" + error;
-										in.waitForPrompt();
-										linetype = in.getLineType();
-										break;
-									}
-								}
-								if (error != null)
-									break;
-								// it is of no use to store
-								// DataBlockReponses, you never want to
-								// retrieve them directly anyway
-								if (!(res instanceof DataBlockResponse))
-									responses.add(res);
-
-								// read the next line (can be prompt, new
-								// result, error, etc.) before we start the
-								// loop over
-								tmpLine = in.readLine();
-								linetype = in.getLineType();
-							break;
-							case AbstractMCLReader.INFO:
-								addWarning(tmpLine.substring(1), "01000");
-
-								// read the next line (can be prompt, new
-								// result, error, etc.) before we start the
-								// loop over
-								tmpLine = in.readLine();
-								linetype = in.getLineType();
-							break;
-							default:	// Yeah... in Java this is correct!
-								// we have something we don't
-								// expect/understand, let's make it an error
-								// message
-								tmpLine = "!M0M10!protocol violation, unexpected line: " + tmpLine;
-								// don't break; fall through...
-							case AbstractMCLReader.ERROR:
-								// read everything till the prompt (should be
-								// error) we don't know if we ignore some
-								// garbage here... but the log should reveal
-								// that
-								error = in.waitForPrompt();
-								linetype = in.getLineType();
-								if (error != null) {
-									error = tmpLine.substring(1) + "\n" + error;
-								} else {
-									error = tmpLine.substring(1);
-								}
-							break;
-						}
-					}
-				}
+    /**
+     * Sets the maximum period a Connection or objects created from the
+     * Connection will wait for the database to reply to any one
+     * request. If any request remains unanswered, the waiting method
+     * will return with a SQLException, and the Connection or objects
+     * created from the Connection will be marked as closed. Any
+     * subsequent use of the objects, with the exception of the close,
+     * isClosed or Connection.isValid methods, will result in a
+     * SQLException.
+     *
+     * @param executor The Executor implementation which will be used by
+     *        setNetworkTimeout
+     * @param millis The time in milliseconds to wait for the
+     *        database operation to complete
+     * @throws SQLException if a database access error occurs, this
+     *         method is called on a closed connection, the executor is
+     *         null, or the value specified for seconds is less than 0.
+     */
+    @Override
+    public void setNetworkTimeout(Executor executor, int millis) throws SQLException {
+        if (closed) {
+            throw new SQLException("Cannot call on closed Connection", "M1M20");
+        }
+        if (executor == null)
+            throw new SQLException("executor is null", "M1M05");
+        if (millis < 0)
+            throw new SQLException("milliseconds is less than zero", "M1M05");
+        try {
+            this.setSoTimeout(millis);
+        } catch (SocketException e) {
+            throw new SQLException(e.getMessage(), "08000");
+        }
+    }
 
-				// if we used the sendThread, make sure it has finished
-				if (sendThreadInUse) {
-					String tmp = sendThread.getErrors();
-					if (tmp != null) {
-						if (error == null) {
-							error = "08000!" + tmp;
-						} else {
-							error += "\n08000!" + tmp;
-						}
-					}
-				}
-				if (error != null) {
-					SQLException ret = null;
-					String[] errors = error.split("\n");
-					for (String error1 : errors) {
-						if (ret == null) {
-							ret = new SQLException(error1.substring(6),
-									error1.substring(0, 5));
-						} else {
-							ret.setNextException(new SQLException(
-									error1.substring(6),
-									error1.substring(0, 5)));
-						}
-					}
-					throw ret;
-				}
-			} catch (SocketTimeoutException e) {
-				close(); // JDBC 4.1 semantics, abort()
-				throw new SQLException("connection timed out", "08M33");
-			} catch (IOException e) {
-				closed = true;
-				throw new SQLException(e.getMessage() + " (mserver still alive?)", "08000");
-			}
-		}
-	}
-	// }}}
+    /**
+     * Retrieves the number of milliseconds the driver will wait for a
+     * database request to complete. If the limit is exceeded, a
+     * SQLException is thrown.
+     *
+     * @return the current timeout limit in milliseconds; zero means
+     *         there is no limit
+     * @throws SQLException if a database access error occurs or
+     *         this method is called on a closed Connection
+     */
+    @Override
+    public int getNetworkTimeout() throws SQLException {
+        if (closed) {
+            throw new SQLException("Cannot call on closed Connection", "M1M20");
+        }
+        try {
+            return this.getSoTimeout();
+        } catch (SocketException e) {
+            throw new SQLException(e.getMessage(), "08000");
+        }
+    }
 
-	/**
-	 * A thread to send a query to the server.  When sending large
-	 * amounts of data to a server, the output buffer of the underlying
-	 * communication socket may overflow.  In such case the sending
-	 * process blocks.  In order to prevent deadlock, it might be
-	 * desirable that the driver as a whole does not block.  This thread
-	 * facilitates the prevention of such 'full block', because this
-	 * separate thread only will block.<br />
-	 * This thread is designed for reuse, as thread creation costs are
-	 * high.
-	 */
-	// {{{ SendThread class implementation
-	static class SendThread extends Thread {
-		/** The state WAIT represents this thread to be waiting for
-		 *  something to do */
-		private final static int WAIT = 0;
-		/** The state QUERY represents this thread to be executing a query */
-		private final static int QUERY = 1;
-		/** The state SHUTDOWN is the final state that ends this thread */
-		private final static int SHUTDOWN = -1;
+    //== end methods of interface Connection
 
-		private String[] templ;
-		private String query;
-		private AbstractMCLWriter out;
-		private String error;
-		private int state = WAIT;
-		
-		final Lock sendLock = new ReentrantLock();
-		final Condition queryAvailable = sendLock.newCondition(); 
-		final Condition waiting = sendLock.newCondition();
-
-		/**
-		 * Constructor which immediately starts this thread and sets it
-		 * into daemon mode.
-		 *
-		 * @param out the socket to write to
-		 */
-		public SendThread(AbstractMCLWriter out) {
-			super("SendThread");
-			setDaemon(true);
-			this.out = out;
-			start();
-		}
+    /**
+     * Returns whether the BLOB type should be mapped to BINARY type.
+     */
+    public boolean getBlobAsBinary() {
+        return blobIsBinary;
+    }
 
-		@Override
-		public void run() {
-			sendLock.lock();
-			try {
-				while (true) {
-					while (state == WAIT) {
-						try {
-							queryAvailable.await();
-						} catch (InterruptedException e) {
-							// woken up, eh?
-						}
-					}
-					if (state == SHUTDOWN)
-						break;
-
-					// state is QUERY here
-					try {
-						out.writeLine((templ[0] == null ? "" : templ[0]) + query + (templ[1] == null ? "" : templ[1]));
-					} catch (IOException e) {
-						error = e.getMessage();
-					}
-
-					// update our state, and notify, maybe someone is waiting
-					// for us in throwErrors
-					state = WAIT;
-					waiting.signal();
-				}
-			} finally {
-				sendLock.unlock();
-			}
-		}
-
-		/**
-		 * Starts sending the given query over the given socket.  Beware
-		 * that the thread should be finished (can be assured by calling
-		 * throwErrors()) before this method is called!
-		 *
-		 * @param templ the query template
-		 * @param query the query itself
-		 * @throws SQLException if this SendThread is already in use
-		 */
-		public void runQuery(String[] templ, String query) throws SQLException {
-			sendLock.lock();
-			try {
-				if (state != WAIT) 
-					throw new SQLException("SendThread already in use or shutting down!", "M0M03");
+    /**
+     * Sends the given string to MonetDB as regular statement, making
+     * sure there is a prompt after the command is sent.  All possible
+     * returned information is discarded.  Encountered errors are
+     * reported.
+     *
+     * @param command the exact string to send to MonetDB
+     * @throws SQLException if an IO exception or a database error occurs
+     */
+    public void sendIndependentCommand(String command) throws SQLException {
+        synchronized (this) {
+            try {
+                out.writeLine(server.getQueryTemplateHeader(0) + command + server.getQueryTemplateHeader(1));
+                String error = in.waitForPrompt();
+                if (error != null)
+                    throw new SQLException(error.substring(6),
+                            error.substring(0, 5));
+            } catch (SocketTimeoutException e) {
+                close(); // JDBC 4.1 semantics: abort()
+                throw new SQLException("connection timed out", "08M33");
+            } catch (IOException e) {
+                throw new SQLException(e.getMessage(), "08000");
+            }
+        }
+    }
 
-				this.templ = templ;
-				this.query = query;
-
-				// let the thread know there is some work to do
-				state = QUERY;
-				queryAvailable.signal();
-			} finally {
-				sendLock.unlock();
-			}
-		}
+    /**
+     * Sends the given string to MonetDB as control statement, making
+     * sure there is a prompt after the command is sent.  All possible
+     * returned information is discarded.  Encountered errors are
+     * reported.
+     *
+     * @param command the exact string to send to MonetDB
+     * @throws SQLException if an IO exception or a database error occurs
+     */
+    public void sendControlCommand(String command) throws SQLException {
+        // send X command
+        synchronized (this) {
+            try {
+                out.writeLine(server.getCommandTemplateHeader(0) + command + server.getCommandTemplateHeader(1));
+                String error = in.waitForPrompt();
+                if (error != null)
+                    throw new SQLException(error.substring(6),
+                            error.substring(0, 5));
+            } catch (SocketTimeoutException e) {
+                close(); // JDBC 4.1 semantics, abort()
+                throw new SQLException("connection timed out", "08M33");
+            } catch (IOException e) {
+                throw new SQLException(e.getMessage(), "08000");
+            }
+        }
+    }
 
-		/**
-		 * Returns errors encountered during the sending process.
-		 *
-		 * @return the errors or null if none
-		 */
-		public String getErrors() {
-			sendLock.lock();
-			try {
-				// make sure the thread is in WAIT state, not QUERY
-				while (state == QUERY) {
-					try {
-						waiting.await();
-					} catch (InterruptedException e) {
-						// just try again
-					}
-				}
-				if (state == SHUTDOWN)
-					error = "SendThread is shutting down";
-			} finally {
-				sendLock.unlock();
-			}
-			return error;
-		}
-
-		/**
-		 * Requests this SendThread to stop. 
-		 */
-		public void shutdown() {
-			sendLock.lock();
-			state = SHUTDOWN;
-			sendLock.unlock();
-			this.interrupt();  // break any wait conditions
-		}
-	}
-	// }}}
+    /**
+     * Adds a warning to the pile of warnings this Connection object
+     * has.  If there were no warnings (or clearWarnings was called)
+     * this warning will be the first, otherwise this warning will get
+     * appended to the current warning.
+     *
+     * @param reason the warning message
+     */
+    public void addWarning(String reason, String sqlstate) {
+        if (warnings == null) {
+            warnings = new SQLWarning(reason, sqlstate);
+        } else {
+            warnings.setNextWarning(new SQLWarning(reason, sqlstate));
+        }
+    }
 }
-
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java
@@ -8,7 +8,6 @@
 
 package nl.cwi.monetdb.jdbc;
 
-import nl.cwi.monetdb.mcl.connection.AbstractMonetDBConnection;
 import java.sql.BatchUpdateException;
 import java.sql.Connection;
 import java.sql.Statement;
@@ -202,7 +201,7 @@ public class MonetStatement extends Mone
 			boolean first = true;
 			boolean error = false;
 
-			AbstractMonetDBConnection server = connection.getServer();
+			MonetConnection server = connection.getServer();
 
 			BatchUpdateException e = new BatchUpdateException("Error(s) occurred while executing the batch, see next SQLExceptions for details", "22000", counts);
 			StringBuilder tmpBatch = new StringBuilder(server.getBlockSize());
deleted file mode 100644
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/AbstractMonetDBConnection.java
+++ /dev/null
@@ -1,204 +0,0 @@
-package nl.cwi.monetdb.mcl.connection;
-
-import nl.cwi.monetdb.mcl.MCLException;
-import nl.cwi.monetdb.mcl.parser.MCLParseException;
-import nl.cwi.monetdb.mcl.protocol.AbstractProtocolParser;
-
-import java.io.*;
-import java.net.SocketException;
-import java.util.List;
-
-/**
- * Created by ferreira on 11/23/16.
- */
-public abstract class AbstractMonetDBConnection {
-
-    /** The language to connect with */
-    protected MonetDBLanguage currentMonetDBLanguage = MonetDBLanguage.LANG_SQL;
-    /** The database to connect to */
-    protected final String database;
-    /** Authentication hash method */
-    protected final String hash;
-    /** Whether we are debugging or not */
-    protected boolean debug;
-    /** The Writer for the debug log-file */
-    protected Writer log;
-
-    public AbstractMonetDBConnection(String database, String hash, boolean debug, MonetDBLanguage lang) throws IOException {
-        this.database = database;
-        this.hash = hash;
-        this.debug = debug;
-        this.currentMonetDBLanguage = lang;
-    }
-
-    public String getDatabase() {
-        return database;
-    }
-
-    public String getHash() {
-        return hash;
-    }
-
-    public boolean isDebug() {
-        return debug;
-    }
-
-    /**
-     * Enables/disables debug
-     *
-     * @param debug Value to set
-     */
-    public void setDebug(boolean debug) {
-        this.debug = debug;
-    }
-
-    public MonetDBLanguage getCurrentMonetDBLanguage() {
-        return currentMonetDBLanguage;
-    }
-
-    public void setCurrentMonetDBLanguage(MonetDBLanguage currentMonetDBLanguage) {
-        this.currentMonetDBLanguage = currentMonetDBLanguage;
-    }
-
-    /**
-     * Connects to the given host and port, logging in as the given
-     * user.  If followRedirect is false, a RedirectionException is
-     * thrown when a redirect is encountered.
-     *
-     * @return A List with informational (warning) messages. If this
-     * 		list is empty; then there are no warnings.
-     * @throws IOException if an I/O error occurs when creating the
-     *         socket
-     * @throws MCLParseException if bogus data is received
-     * @throws MCLException if an MCL related error occurs
-     */
-    public abstract List<String> connect(String user, String pass) throws IOException, MCLParseException, MCLException;
-
-    /**
-     * Enables logging to a file what is read and written from and to
-     * the server.  Logging can be enabled at any time.  However, it is
-     * encouraged to start debugging before actually connecting the
-     * socket.
-     *
-     * @param filename the name of the file to write to
-     * @throws IOException if the file could not be opened for writing
-     */
-    public void debug(String filename) throws IOException {
-        debug(new FileWriter(filename));
-    }
-
-    /**
-     * Enables logging to a stream what is read and written from and to
-     * the server.  Logging can be enabled at any time.  However, it is
-     * encouraged to start debugging before actually connecting the
-     * socket.
-     *
-     * @param out to write the log to
-     * @throws IOException if the file could not be opened for writing
-     */
-    public void debug(PrintStream out) throws IOException {
-        debug(new PrintWriter(out));
-    }
-
-    /**
-     * Enables logging to a stream what is read and written from and to
-     * the server.  Logging can be enabled at any time.  However, it is
-     * encouraged to start debugging before actually connecting the
-     * socket.
-     *
-     * @param out to write the log to
-     * @throws IOException if the file could not be opened for writing
-     */
-    public void debug(Writer out) throws IOException {
-        log = out;
-        debug = true;
-    }
-
-    /**
-     * Writes a logline tagged with a timestamp using the given string.
-     * Used for debugging purposes only and represents a message that is
-     * connected to writing to the socket.  A logline might look like:
-     * TX 152545124: Hello MonetDB!
-     *
-     * @param message the message to log
-     * @throws IOException if an IO error occurs while writing to the logfile
-     */
-    private void logTx(String message) throws IOException {
-        log.write("TX " + System.currentTimeMillis() +
-                ": " + message + "\n");
-    }
-
-    /**
-     * Writes a logline tagged with a timestamp using the given string.
-     * Lines written using this log method are tagged as "added
-     * metadata" which is not strictly part of the data sent.
-     *
-     * @param message the message to log
-     * @throws IOException if an IO error occurs while writing to the logfile
-     */
-    private void logTd(String message) throws IOException {
-        log.write("TD " + System.currentTimeMillis() +
-                ": " + message + "\n");
-    }
-
-    /**
-     * Writes a logline tagged with a timestamp using the given string,
-     * and flushes afterwards.  Used for debugging purposes only and
-     * represents a message that is connected to reading from the
-     * socket.  The log is flushed after writing the line.  A logline
-     * might look like:
-     * RX 152545124: Hi JDBC!
-     *
-     * @param message the message to log
-     * @throws IOException if an IO error occurs while writing to the logfile
-     */
-    private void logRx(String message) throws IOException {
-        log.write("RX " + System.currentTimeMillis() +
-                ": " + message + "\n");
-        log.flush();
-    }
-
-    /**
-     * Writes a logline tagged with a timestamp using the given string,
-     * and flushes afterwards.  Lines written using this log method are
-     * tagged as "added metadata" which is not strictly part of the data
-     * received.
-     *
-     * @param message the message to log
-     * @throws IOException if an IO error occurs while writing to the logfile
-     */
-    private void logRd(String message) throws IOException {
-        log.write("RD " + System.currentTimeMillis() +
-                ": " + message + "\n");
-        log.flush();
-    }
-
-    public synchronized void close() {
-        try {
-            if (debug && log instanceof FileWriter) log.close();
-        } catch (IOException e) {
-            // ignore it
-        }
-    }
-
-    /**
-     * Destructor called by garbage collector before destroying this
-     * object tries to disconnect the MonetDB connection if it has not
-     * been disconnected already.
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        this.close();
-        super.finalize();
-    }
-
-    public abstract String getJDBCURL();
-
-    public abstract int getBlockSize();
-
-    public abstract int getSoTimeout() throws SocketException;
-
-    public abstract void setSoTimeout(int s) throws SocketException;
-
-    public abstract AbstractProtocolParser getUnderlyingProtocol();
-}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/Debugger.java
@@ -0,0 +1,111 @@
+package nl.cwi.monetdb.mcl.connection;
+
+import java.io.*;
+
+/**
+ * Created by ferreira on 12/1/16.
+ */
+public class Debugger implements Closeable {
+
+    /** The Writer for the debug log-file */
+    private final Writer log;
+
+    /**
+     * Enables logging to a stream what is read and written from and to
+     * the server.  Logging can be enabled at any time.  However, it is
+     * encouraged to start debugging before actually connecting the
+     * socket.
+     *
+     * @param log to write the log to
+     */
+    public Debugger(Writer log) {
+        this.log = log;
+    }
+
+    /**
+     * Enables logging to a file what is read and written from and to
+     * the server.  Logging can be enabled at any time.  However, it is
+     * encouraged to start debugging before actually connecting the
+     * socket.
+     *
+     * @param filename the name of the file to write to
+     * @throws IOException if the file could not be opened for writing
+     */
+    public Debugger(String filename) throws IOException {
+        this.log = new FileWriter(filename);
+    }
+
+    /**
+     * Enables logging to a stream what is read and written from and to
+     * the server.  Logging can be enabled at any time.  However, it is
+     * encouraged to start debugging before actually connecting the
+     * socket.
+     *
+     * @param out to write the log to
+     */
+    public Debugger(PrintStream out) {
+        this.log = new PrintWriter(out);
+    }
+
+    /**
+     * Writes a logline tagged with a timestamp using the given string.
+     * Used for debugging purposes only and represents a message that is
+     * connected to writing to the socket.  A logline might look like:
+     * TX 152545124: Hello MonetDB!
+     *
+     * @param message the message to log
+     * @throws IOException if an IO error occurs while writing to the logfile
+     */
+    private void logTx(String message) throws IOException {
+        log.write("TX " + System.currentTimeMillis() + ": " + message + "\n");
+    }
+
+    /**
+     * Writes a logline tagged with a timestamp using the given string.
+     * Lines written using this log method are tagged as "added
+     * metadata" which is not strictly part of the data sent.
+     *
+     * @param message the message to log
+     * @throws IOException if an IO error occurs while writing to the logfile
+     */
+    private void logTd(String message) throws IOException {
+        log.write("TD " + System.currentTimeMillis() + ": " + message + "\n");
+    }
+
+    /**
+     * Writes a logline tagged with a timestamp using the given string,
+     * and flushes afterwards.  Used for debugging purposes only and
+     * represents a message that is connected to reading from the
+     * socket.  The log is flushed after writing the line.  A logline
+     * might look like:
+     * RX 152545124: Hi JDBC!
+     *
+     * @param message the message to log
+     * @throws IOException if an IO error occurs while writing to the logfile
+     */
+    private void logRx(String message) throws IOException {
+        log.write("RX " + System.currentTimeMillis() + ": " + message + "\n");
+        log.flush();
+    }
+
+    /**
+     * Writes a logline tagged with a timestamp using the given string,
+     * and flushes afterwards.  Lines written using this log method are
+     * tagged as "added metadata" which is not strictly part of the data
+     * received.
+     *
+     * @param message the message to log
+     * @throws IOException if an IO error occurs while writing to the logfile
+     */
+    private void logRd(String message) throws IOException {
+        log.write("RD " + System.currentTimeMillis() + ": " + message + "\n");
+        log.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (log instanceof FileWriter) {
+            log.close();
+        }
+    }
+}
deleted file mode 100644
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/DeleteMe.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0.  If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
- */
-
-package nl.cwi.monetdb.mcl.connection;
-
-import java.io.IOException;
-
-
-public final class DeleteMe extends MapiConnection {
-
-
-	public DeleteMe(String database, boolean debug, MonetDBLanguage lang, String hostname, int port) throws IOException {
-		super(database, debug, lang, hostname, port, 9);
-	}
-
-}
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/EmbeddedMonetDB.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/EmbeddedMonetDB.java
@@ -2,7 +2,7 @@ package nl.cwi.monetdb.mcl.connection;
 
 import nl.cwi.monetdb.embedded.env.MonetDBEmbeddedDatabase;
 import nl.cwi.monetdb.embedded.env.MonetDBEmbeddedException;
-import nl.cwi.monetdb.mcl.MCLException;
+import nl.cwi.monetdb.jdbc.MonetConnection;
 import nl.cwi.monetdb.mcl.io.*;
 import nl.cwi.monetdb.mcl.parser.MCLParseException;
 
@@ -13,21 +13,17 @@ import java.util.List;
 /**
  * Created by ferreira on 11/23/16.
  */
-public final class EmbeddedMonetDB extends AbstractMonetDBConnection {
+public final class EmbeddedMonetDB extends MonetConnection {
 
     private final String directory;
 
     private InternalConnection connection;
 
-    public EmbeddedMonetDB(String database, String hash, boolean debug, MonetDBLanguage lang, String directory) throws IOException {
-        super(database, hash, debug, lang);
+    public EmbeddedMonetDB(String database, String hash, String language, boolean blobIsBinary, boolean isDebugging, String directory) throws IOException {
+        super(database, hash, language, blobIsBinary, isDebugging);
         this.directory = directory;
     }
 
-    public String getDirectory() {
-        return directory;
-    }
-
     @Override
     public List<String> connect(String user, String pass) throws IOException, MCLParseException, MCLException {
         try {
@@ -44,18 +40,8 @@ public final class EmbeddedMonetDB exten
     }
 
     @Override
-    public synchronized void close() {
-        super.close();
-        try {
-            MonetDBEmbeddedDatabase.StopDatabase();
-        } catch (MonetDBEmbeddedException e) {
-            // ignore it
-        }
-    }
-
-    @Override
     public String getJDBCURL() {
-        return "jdbc:monetdb://localhost@" + this.getDirectory() + "/" + this.getDatabase();
+        return "jdbc:monetdb://localhost@" + this.directory + "/" + this.database;
     }
 
     @Override
@@ -65,11 +51,20 @@ public final class EmbeddedMonetDB exten
 
     @Override
     public int getSoTimeout() throws SocketException {
-        throw new IllegalArgumentException("Cannot get a timeout on a embedded connection!");
+        throw new SocketException("Cannot get a timeout on a embedded connection!");
     }
 
     @Override
     public void setSoTimeout(int s) throws SocketException {
-        throw new IllegalArgumentException("Cannot set a timeout on a embedded connection!");
+        throw new SocketException("Cannot set a timeout on a embedded connection!");
+    }
+
+    @Override
+    public void closeUnderlyingConnection() throws IOException {
+        try {
+            MonetDBEmbeddedDatabase.StopDatabase();
+        } catch (MonetDBEmbeddedException e) {
+            // ignore it
+        }
     }
 }
rename from src/main/java/nl/cwi/monetdb/mcl/MCLException.java
rename to src/main/java/nl/cwi/monetdb/mcl/connection/MCLException.java
--- a/src/main/java/nl/cwi/monetdb/mcl/MCLException.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MCLException.java
@@ -6,7 +6,7 @@
  * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
  */
 
-package nl.cwi.monetdb.mcl;
+package nl.cwi.monetdb.mcl.connection;
 
 /**
  * A general purpose Exception class for MCL related problems.  This
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/MapiConnection.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MapiConnection.java
@@ -1,22 +1,18 @@
 package nl.cwi.monetdb.mcl.connection;
 
-import nl.cwi.monetdb.mcl.MCLException;
+import nl.cwi.monetdb.jdbc.MonetConnection;
 import nl.cwi.monetdb.mcl.io.BufferedMCLReader;
 import nl.cwi.monetdb.mcl.io.BufferedMCLWriter;
 import nl.cwi.monetdb.mcl.io.SocketConnection;
-import nl.cwi.monetdb.mcl.io.SocketIOHandler;
 import nl.cwi.monetdb.mcl.parser.MCLParseException;
-import nl.cwi.monetdb.mcl.protocol.AbstractProtocolParser;
 import nl.cwi.monetdb.mcl.protocol.ServerResponses;
 import nl.cwi.monetdb.mcl.protocol.oldmapi.OldMapiProtocol;
+import nl.cwi.monetdb.util.ChannelSecurity;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.SocketException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.*;
 
 /**
@@ -69,26 +65,7 @@ import java.util.*;
  * @see BufferedMCLReader
  * @see BufferedMCLWriter
  */
-public class MapiConnection extends AbstractMonetDBConnection {
-
-    private static char hexChar(int n) { return (n > 9) ? (char) ('a' + (n - 10)) : (char) ('0' + n); }
-
-    /**
-     * Small helper method to convert a byte string to a hexadecimal
-     * string representation.
-     *
-     * @param digest the byte array to convert
-     * @return the byte array as hexadecimal string
-     */
-    private static String toHex(byte[] digest) {
-        char[] result = new char[digest.length * 2];
-        int pos = 0;
-        for (byte aDigest : digest) {
-            result[pos++] = hexChar((aDigest & 0xf0) >> 4);
-            result[pos++] = hexChar(aDigest & 0x0f);
-        }
-        return new String(result);
-    }
+public class MapiConnection extends MonetConnection {
 
     /** The hostname to connect to */
     protected final String hostname;
@@ -103,20 +80,12 @@ public class MapiConnection extends Abst
 
     protected OldMapiProtocol protocol;
 
-    public MapiConnection(String database, String hash, boolean debug, MonetDBLanguage lang, String hostname, int port) throws IOException {
-        super(database, hash, debug, lang);
+    public MapiConnection(String database, String hash, String language, boolean blobIsBinary, boolean isDebugging, String hostname, int port) throws IOException {
+        super(database, hash, language, blobIsBinary, isDebugging);
         this.hostname = hostname;
         this.port = port;
     }
 
-    public String getHostname() {
-        return hostname;
-    }
-
-    public int getPort() {
-        return port;
-    }
-
     /**
      * Sets whether MCL redirections should be followed or not.  If set
      * to false, an MCLException will be thrown when a redirect is
@@ -156,76 +125,82 @@ public class MapiConnection extends Abst
     }
 
     @Override
+    public int getBlockSize() {
+        return protocol.getConnection().getBlockSize();
+    }
+
+    @Override
+    public int getSoTimeout() throws SocketException {
+        return protocol.getConnection().getSoTimeout();
+    }
+
+    @Override
+    public void setSoTimeout(int s) throws SocketException {
+        protocol.getConnection().setSoTimeout(s);
+    }
+
+    @Override
+    public void closeUnderlyingConnection() throws IOException {
+        protocol.getConnection().close();
+    }
+
+    @Override
     public String getJDBCURL() {
         String language = "";
         if (this.getCurrentMonetDBLanguage() == MonetDBLanguage.LANG_MAL)
             language = "?language=mal";
-        return "jdbc:monetdb://" + this.getHostname() + ":" + this.getPort() + "/" + this.getDatabase() + language;
+        return "jdbc:monetdb://" + this.hostname + ":" + this.port + "/" + this.database + language;
     }
 
     @Override
-    public void close() {
-        super.close();
-        try {
-            protocol.getHandler().getConnection().close();
-        } catch (IOException e) {
-            // ignore it
-        }
-    }
-
-    public int getBlockSize() {
-        return protocol.getHandler().getConnection().getBlockSize();
+    public List<String> connect(String user, String pass) throws IOException, MCLParseException, MCLException {
+        // Wrap around the internal connect that needs to know if it
+        // should really make a TCP connection or not.
+        List<String> res = connect(this.hostname, this.port, user, pass, true);
+        // apply NetworkTimeout value from legacy (pre 4.1) driver
+        // so_timeout calls
+        this.setSoTimeout(this.getSoTimeout());
+        return res;
     }
 
-    public int getSoTimeout() throws SocketException {
-        return protocol.getHandler().getConnection().getSoTimeout();
-    }
-
-    public void setSoTimeout(int s) throws SocketException {
-        protocol.getHandler().getConnection().setSoTimeout(s);
-    }
-
-    @Override
-    public AbstractProtocolParser getUnderlyingProtocol() {
-        return protocol;
-    }
-
-    private List<String> connect(String user, String pass, boolean makeConnection) throws IOException, MCLParseException, MCLException {
+    private List<String> connect(String host, int port, String user, String pass, boolean makeConnection) throws IOException, MCLParseException, MCLException {
         if (ttl-- <= 0)
             throw new MCLException("Maximum number of redirects reached, aborting connection attempt.  Sorry.");
 
         if (makeConnection) {
             this.protocol = new OldMapiProtocol(new SocketConnection(this.hostname, this.port));
+            this.protocol.getConnection().setTcpNoDelay(true);
+
             // set nodelay, as it greatly speeds up small messages (like we
             // often do)
-            this.protocol.getHandler().getConnection().setTcpNoDelay(true);
-            //TODO writer.registerReader(reader);
+            //TODO writer.registerReader(reader); ??
         }
 
-        ServerResponses nextResponse;
-
-        String test = getChallengeResponse(user, pass, language, database, hash);
+        this.protocol.getNextResponseHeader();
+        String test = this.getChallengeResponse(this.protocol.getEntireResponseLine(), user, pass,
+                this.currentMonetDBLanguage.getRepresentation(), this.database, this.hash);
+        this.protocol.writeNextLine(test.getBytes());
 
-        writer.writeLine();
-
-        // read monet response till prompt
         List<String> redirects = new ArrayList<>();
         List<String> warns = new ArrayList<>();
-        String err = "", tmp;
-        int lineType;
+        String err = "";
+        ServerResponses next;
+
         do {
-            if ((tmp = reader.readLine()) == null)
-                throw new IOException("Read from " + this.getHostname() + ":" + this.getPort() + ": End of stream reached");
-            if ((lineType = reader.getLineType()) == BufferedMCLReader.ERROR) {
-                err += "\n" + tmp.substring(7);
-            } else if (lineType == BufferedMCLReader.INFO) {
-                warns.add(tmp.substring(1));
-            } else if (lineType == BufferedMCLReader.REDIRECT) {
-                redirects.add(tmp.substring(1));
+            next = this.protocol.getNextResponseHeader();
+            switch (next) {
+                case ERROR:
+                    err += "\n" + this.protocol.getRemainingResponseLine(0);
+                    break;
+                case INFO:
+                    warns.add(this.protocol.getRemainingResponseLine(0));
+                case REDIRECT:
+                    redirects.add(this.protocol.getRemainingResponseLine(0));
             }
-        } while (lineType != BufferedMCLReader.PROMPT);
+        } while (next != ServerResponses.PROMPT);
+
         if (!err.equals("")) {
-            close();
+            this.close();
             throw new MCLException(err.trim());
         }
         if (!redirects.isEmpty()) {
@@ -250,7 +225,7 @@ public class MapiConnection extends Abst
                     throw new MCLParseException(e.toString());
                 }
 
-                tmp = u.getQuery();
+                String tmp = u.getQuery();
                 if (tmp != null) {
                     String args[] = tmp.split("&");
                     for (String arg : args) {
@@ -261,15 +236,14 @@ public class MapiConnection extends Abst
                                 case "database":
                                     tmp = arg.substring(pos + 1);
                                     if (!tmp.equals(database)) {
-                                        warns.add("redirect points to different " +
-                                                "database: " + tmp);
-                                        setDatabase(tmp);
+                                        warns.add("redirect points to different " + "database: " + tmp);
+                                        this.database = tmp;
                                     }
                                     break;
                                 case "language":
                                     tmp = arg.substring(pos + 1);
                                     warns.add("redirect specifies use of different language: " + tmp);
-                                    setLanguage(tmp);
+                                     this.currentMonetDBLanguage = MonetDBLanguage.GetLanguageFromString(tmp);
                                     break;
                                 case "user":
                                     tmp = arg.substring(pos + 1);
@@ -296,20 +270,19 @@ public class MapiConnection extends Abst
                         // this is a redirect to another (monetdb) server,
                         // which means a full reconnect
                         // avoid the debug log being closed
-                        if (debug) {
-                            debug = false;
-                            close();
-                            debug = true;
+                        if (this.isDebugging) {
+                            this.isDebugging = false;
+                            this.close();
+                            this.isDebugging = true;
                         } else {
-                            close();
+                            this.close();
                         }
                         tmp = u.getPath();
                         if (tmp != null && tmp.length() != 0) {
                             tmp = tmp.substring(1).trim();
                             if (!tmp.isEmpty() && !tmp.equals(database)) {
-                                warns.add("redirect points to different " +
-                                        "database: " + tmp);
-                                setDatabase(tmp);
+                                warns.add("redirect points to different " + "database: " + tmp);
+                                this.database = tmp;
                             }
                         }
                         int p = u.getPort();
@@ -335,17 +308,6 @@ public class MapiConnection extends Abst
         return warns;
     }
 
-    @Override
-    public List<String> connect(String user, String pass) throws IOException, MCLParseException, MCLException {
-        // Wrap around the internal connect that needs to know if it
-        // should really make a TCP connection or not.
-        List<String> res = connect(user, pass, true);
-        // apply NetworkTimeout value from legacy (pre 4.1) driver
-        // so_timeout calls
-        this.setSoTimeout(this.getSoTimeout());
-        return res;
-    }
-
     /**
      * A little helper function that processes a challenge string, and
      * returns a response string for the server.  If the challenge
@@ -359,7 +321,7 @@ public class MapiConnection extends Abst
      * @param hash the hash method(s) to use, or NULL for all supported
      *             hashes
      */
-    private String getChallengeResponse(String username, String password, String language, String database, String hash)
+    private String getChallengeResponse(String chalstr, String username, String password, String language, String database, String hash)
             throws MCLParseException, MCLException, IOException {
         String response;
         String algo;
@@ -409,14 +371,7 @@ public class MapiConnection extends Abst
                         throw new MCLException("Unsupported password hash: " + chaltok[5]);
                 }
 
-                try {
-                    MessageDigest md = MessageDigest.getInstance(algo);
-                    md.update(password.getBytes("UTF-8"));
-                    byte[] digest = md.digest();
-                    password = toHex(digest);
-                } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
-                    throw new AssertionError("internal error: " + e.toString());
-                }
+                password = ChannelSecurity.DigestStrings(algo, password);
 
                 // proto 7 (finally) used the challenge and works with a
                 // password hash.  The supported implementations come
@@ -454,17 +409,11 @@ public class MapiConnection extends Abst
                     algo = "MD5";
                     pwhash = "{MD5}";
                 } else {
-                    throw new MCLException("no supported password hashes in " + hashes);
+                    throw new MCLException("No supported password hashes in " + hashes);
                 }
-                try {
-                    MessageDigest md = MessageDigest.getInstance(algo);
-                    md.update(password.getBytes("UTF-8"));
-                    md.update(challenge.getBytes("UTF-8"));
-                    byte[] digest = md.digest();
-                    pwhash += toHex(digest);
-                } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
-                    throw new AssertionError("internal error: " + e.toString());
-                }
+
+                pwhash += ChannelSecurity.DigestStrings(algo, password, challenge);
+
                 // TODO: some day when we need this, we should store
                 // this
                 switch (chaltok[4]) {
@@ -486,5 +435,4 @@ public class MapiConnection extends Abst
                 return response;
         }
     }
-
 }
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java
@@ -0,0 +1,152 @@
+package nl.cwi.monetdb.mcl.connection;
+
+import nl.cwi.monetdb.jdbc.MonetConnection;
+import nl.cwi.monetdb.mcl.parser.MCLParseException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.SocketException;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Created by ferreira on 12/1/16.
+ */
+public final class MonetDBConnectionFactory {
+
+    public static MonetConnection CreateMonetDBJDBCConnection(Properties props) throws SQLException, IllegalArgumentException {
+        MonetConnection res;
+
+        boolean isEmbedded = Boolean.parseBoolean(props.getProperty("embedded", "false"));
+        boolean debug = Boolean.valueOf(props.getProperty("debug", "false"));
+        boolean blobIsBinary = Boolean.valueOf(props.getProperty("treat_blob_as_binary", "false"));
+        String language = props.getProperty("language", "sql");
+
+        String username = props.getProperty("user", null);
+        String password = props.getProperty("password", null);
+        String database = props.getProperty("database");
+        if (database == null || database.trim().isEmpty())
+            throw new IllegalArgumentException("database should not be null or empty");
+        String hash = props.getProperty("hash");
+        int sockTimeout = 0;
+
+        //instantiate the connection
+        if(isEmbedded) {
+            String directory = props.getProperty("directory");
+            if (directory == null || directory.trim().isEmpty())
+                throw new IllegalArgumentException("directory should not be null or empty");
+            try {
+                res = new EmbeddedMonetDB(database, hash, language, blobIsBinary, debug, directory);
+            } catch (IOException e) {
+                throw new SQLException(e);
+            }
+        } else {
+            String hostname = props.getProperty("host");
+            if (hostname == null || hostname.trim().isEmpty())
+                throw new IllegalArgumentException("hostname should not be null or empty");
+            int port = 0;
+            try {
+                port = Integer.parseInt(props.getProperty("port"));
+            } catch (NumberFormatException e) {
+            }
+            if (port <= 0)
+                throw new IllegalArgumentException("port should not be 0 or less");
+            if (username == null || username.trim().isEmpty())
+                throw new IllegalArgumentException("user should not be null or empty");
+            if (password == null || password.trim().isEmpty())
+                throw new IllegalArgumentException("password should not be null or empty");
+
+            try {
+                res = new MapiConnection(database, hash, language, blobIsBinary, debug, hostname, port);
+            } catch (IOException e) {
+                throw new SQLException(e);
+            }
+            String timout = props.getProperty("so_timeout", "0");
+            try {
+                sockTimeout = Integer.parseInt(timout);
+            } catch (NumberFormatException e) {
+                res.addWarning("Unable to parse socket timeout number from: " + timout, "M1M05");
+            }
+            if (sockTimeout < 0) {
+                res.addWarning("Negative socket timeout not allowed. Value ignored", "M1M05");
+                sockTimeout = 0;
+            }
+            try {
+                res.setSoTimeout(sockTimeout);
+            } catch (SocketException e) {
+                res.addWarning("The socket timeout could not be set", "M1M05");
+            }
+        }
+
+        //initialize the debugging stuff if so
+        if (debug) {
+            try {
+                String fname = props.getProperty("logfile", "monet_" + System.currentTimeMillis() + ".log");
+                File f = new File(fname);
+                int ext = fname.lastIndexOf('.');
+                if (ext < 0) ext = fname.length();
+                String pre = fname.substring(0, ext);
+                String suf = fname.substring(ext);
+                for (int i = 1; f.exists(); i++) {
+                    f = new File(pre + "-" + i + suf);
+                }
+                res.setDebugging(f.getAbsolutePath());
+            } catch (IOException ex) {
+                throw new SQLException("Opening logfile failed: " + ex.getMessage(), "08M01");
+            }
+        }
+
+        try {
+            List<String> warnings = res.connect(username, password);
+            for (String warning : warnings) {
+                res.addWarning(warning, "01M02");
+            }
+
+            // apply NetworkTimeout value from legacy (pre 4.1) driver
+            // so_timeout calls
+            if(!isEmbedded) {
+                res.setSoTimeout(sockTimeout);
+            }
+
+
+            in = server.getReader();
+            out = server.getWriter();
+
+            String error = in.waitForPrompt();
+            if (error != null)
+                throw new SQLException((error.length() > 6) ? error.substring(6) : error, "08001");
+        } catch (IOException e) {
+            throw new SQLException("Unable to connect (" + hostname + ":" + port + "): " + e.getMessage(), "08006");
+        } catch (MCLParseException e) {
+            throw new SQLException(e.getMessage(), "08001");
+        } catch (MCLException e) {
+            String[] connex = e.getMessage().split("\n");
+            SQLException sqle = new SQLException(connex[0], "08001", e);
+            for (int i = 1; i < connex.length; i++) {
+                sqle.setNextException(new SQLException(connex[1], "08001"));
+            }
+            throw sqle;
+        }
+
+        if (res.getCurrentMonetDBLanguage() == MonetDBLanguage.LANG_SQL) {
+            // enable auto commit
+            res.setAutoCommit(true);
+
+            // set our time zone on the server
+            Calendar cal = Calendar.getInstance();
+            int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
+            offset /= (60 * 1000); // milliseconds to minutes
+            String tz = offset < 0 ? "-" : "+";
+            tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":";
+            offset -= (offset / 60) * 60;
+            tz += (offset < 10 ? "0" : "") + offset;
+
+            // TODO check this
+            res.sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE");
+        }
+
+        return res;
+    }
+}
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBLanguage.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBLanguage.java
@@ -6,26 +6,44 @@ package nl.cwi.monetdb.mcl.connection;
 public enum MonetDBLanguage {
 
     /** the SQL language */
-    LANG_SQL(new byte[][]{"s".getBytes(), "\n;".getBytes(), "\n;\n".getBytes()}, new byte[][]{"X".getBytes(), null, "\nX".getBytes()}),
+    LANG_SQL(new byte[][]{"s".getBytes(), "\n;".getBytes(), "\n;\n".getBytes()}, new byte[][]{"X".getBytes(), null, "\nX".getBytes()}, "sql"),
     /** the MAL language (officially *NOT* supported) */
-    LANG_MAL(new byte[][]{null, ";\n".getBytes(), ";\n".getBytes()}, new byte[][]{null, null, null}),
+    LANG_MAL(new byte[][]{null, ";\n".getBytes(), ";\n".getBytes()}, new byte[][]{null, null, null}, "mal"),
     /** an unknown language */
-    LANG_UNKNOWN(null, null);
+    LANG_UNKNOWN(null, null, "unknown");
 
-    MonetDBLanguage(byte[][] queryTemplate, byte[][] commandTemplate) {
-        this.queryTemplate = queryTemplate;
-        this.commandTemplate = commandTemplate;
+    MonetDBLanguage(byte[][] queryTemplates, byte[][] commandTemplates, String representation) {
+        this.queryTemplates = queryTemplates;
+        this.commandTemplates = commandTemplates;
+        this.representation = representation;
     }
 
-    private final byte[][] queryTemplate;
+    private final byte[][] queryTemplates;
 
-    private final byte[][] commandTemplate;
+    private final byte[][] commandTemplates;
+
+    private final String representation;
 
     public byte[] getQueryTemplateIndex(int index) {
-        return queryTemplate[index];
+        return queryTemplates[index];
     }
 
     public byte[] getCommandTemplateIndex(int index) {
-        return commandTemplate[index];
+        return commandTemplates[index];
+    }
+
+    public String getRepresentation() {
+        return representation;
+    }
+
+    public static MonetDBLanguage GetLanguageFromString(String language) {
+        switch (language) {
+            case "sql":
+                return LANG_SQL;
+            case "mal":
+                return LANG_MAL;
+            default:
+                return LANG_UNKNOWN;
+        }
     }
 }
--- a/src/main/java/nl/cwi/monetdb/mcl/io/SocketConnection.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/io/SocketConnection.java
@@ -15,14 +15,38 @@ public class SocketConnection implements
     /** The blocksize (hardcoded in compliance with stream.mx) */
     private static final int BLOCK = 8 * 1024 - 2;
 
+    private static final int CHAR_SIZE = Character.SIZE / Byte.SIZE;
+
+    private static final int SHORT_SIZE = Short.SIZE / Byte.SIZE;
+
+    private static final int INTEGER_SIZE = Integer.SIZE / Byte.SIZE;
+
+    private static final int LONG_SIZE = Long.SIZE / Byte.SIZE;
+
+    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
+
+    private static final int DOUBLE_SIZE = Double.SIZE / Byte.SIZE;
+
+    private static final int INTERMEDIATE_BUFFER_SIZE = 1024;
+
+    /* Local variables */
+    private boolean hasFinished;
+
+    /** Bytebuffers */
+    private final ByteBuffer bufferIn = ByteBuffer.allocateDirect(INTERMEDIATE_BUFFER_SIZE);
+
+    private final ByteBuffer bufferOut = ByteBuffer.allocateDirect(INTERMEDIATE_BUFFER_SIZE);
+
     /** The socket channel */
-    private SocketChannel connection;
+    private final SocketChannel connection;
 
     public SocketConnection(String hostname, int port) throws IOException {
         this.connection = SocketChannel.open(new InetSocketAddress(hostname, port));
         this.connection.configureBlocking(true);
     }
 
+    /* Socket Channel methods */
+
     public int getSoTimeout() throws SocketException {
         return connection.socket().getSoTimeout();
     }
@@ -49,6 +73,121 @@ public class SocketConnection implements
 
     @Override
     public void close() throws IOException {
-        connection.close();
+        this.hasFinished = true;
+        this.connection.close();
+    }
+
+    /* Byte buffer methods */
+
+    private void refillBufferIn() throws IOException {
+        bufferIn.compact();
+        if(!hasFinished) {
+            try {
+                connection.read(this.bufferIn);
+                bufferIn.flip();
+            } catch (IOException ex) {
+                hasFinished = true;
+            }
+        } else {
+            throw new IOException("Done!");
+        }
+    }
+
+
+    public byte readNextByte() throws IOException {
+        if(this.bufferIn.remaining() < Byte.SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.get();
+    }
+
+    public char readNextChar() throws IOException {
+        if(this.bufferIn.remaining() < CHAR_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getChar();
+    }
+
+    public short readNextShort() throws IOException {
+        if(this.bufferIn.remaining() < SHORT_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getShort();
+    }
+
+    public int readNextInt() throws IOException {
+        if(this.bufferIn.remaining() < INTEGER_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getInt();
+    }
+
+    public long readNextLong() throws IOException {
+        if(this.bufferIn.remaining() < LONG_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getLong();
+    }
+
+    public float readNextFloat() throws IOException {
+        if(this.bufferIn.remaining() < FLOAT_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getFloat();
+    }
+
+    public double readNextDouble() throws IOException {
+        if(this.bufferIn.remaining() < DOUBLE_SIZE) {
+            this.refillBufferIn();
+        }
+        return this.bufferIn.getDouble();
+    }
+
+    public int readUntilChar(StringBuilder builder, char limit) throws IOException {
+        builder.setLength(0);
+        boolean found = false;
+
+        while(!found) {
+            if (this.bufferIn.remaining() < CHAR_SIZE) {
+                this.refillBufferIn();
+            }
+            char next = this.bufferIn.getChar();
+            builder.append(next);
+            if(next == limit) {
+                found = true;
+            }
+        }
+        return builder.length();
+    }
+
+    public void writeNextLine(byte[] line) throws IOException {
+        bufferOut.clear();
+        this.writeNextBlock(line);
+        if (bufferOut.hasRemaining()) {
+            bufferOut.flip();
+            connection.write(this.bufferOut);
+        }
+    }
+
+    public void writeNextLine(byte[] prefix, String line, byte[] suffix) throws IOException {
+        bufferOut.clear();
+        this.writeNextBlock(prefix);
+        this.writeNextBlock(line.getBytes());
+        this.writeNextBlock(suffix);
+        if (bufferOut.hasRemaining()) {
+            bufferOut.flip();
+            connection.write(this.bufferOut);
+        }
+    }
+
+    private void writeNextBlock(byte[] block) throws IOException {
+        for (byte aBlock : block) {
+            if (!bufferOut.hasRemaining()) {
+                bufferOut.flip();
+                connection.write(this.bufferOut);
+                bufferOut.clear();
+            }
+            bufferOut.put(aBlock);
+        }
     }
 }
deleted file mode 100644
--- a/src/main/java/nl/cwi/monetdb/mcl/io/SocketIOHandler.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package nl.cwi.monetdb.mcl.io;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Created by ferreira on 11/29/16.
- */
-public class SocketIOHandler {
-
-    private static final int CHAR_SIZE = Character.SIZE / Byte.SIZE;
-
-    private static final int SHORT_SIZE = Short.SIZE / Byte.SIZE;
-
-    private static final int INTEGER_SIZE = Integer.SIZE / Byte.SIZE;
-
-    private static final int LONG_SIZE = Long.SIZE / Byte.SIZE;
-
-    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
-
-    private static final int DOUBLE_SIZE = Double.SIZE / Byte.SIZE;
-
-    private static final int INTERMEDIATE_BUFFER_SIZE = 1024;
-
-    private boolean hasFinished;
-
-    private ByteBuffer bufferIn = ByteBuffer.allocateDirect(INTERMEDIATE_BUFFER_SIZE);
-
-    private ByteBuffer bufferOut = ByteBuffer.allocateDirect(INTERMEDIATE_BUFFER_SIZE);
-
-    private final SocketConnection connection;
-
-    public SocketIOHandler(SocketConnection connection) {
-        this.connection = connection;
-    }
-
-    public SocketConnection getConnection() {
-        return connection;
-    }
-
-    public byte readNextByte() throws IOException {
-        if(this.bufferIn.remaining() < Byte.SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.get();
-    }
-
-    public char readNextChar() throws IOException {
-        if(this.bufferIn.remaining() < CHAR_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getChar();
-    }
-
-    public short readNextShort() throws IOException {
-        if(this.bufferIn.remaining() < SHORT_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getShort();
-    }
-
-    public int readNextInt() throws IOException {
-        if(this.bufferIn.remaining() < INTEGER_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getInt();
-    }
-
-    public long readNextLong() throws IOException {
-        if(this.bufferIn.remaining() < LONG_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getLong();
-    }
-
-    public float readNextFloat() throws IOException {
-        if(this.bufferIn.remaining() < FLOAT_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getFloat();
-    }
-
-    public double readNextDouble() throws IOException {
-        if(this.bufferIn.remaining() < DOUBLE_SIZE) {
-            this.refillBufferIn();
-        }
-        return this.bufferIn.getDouble();
-    }
-
-    public void readUntilChar(StringBuilder builder, char limit) throws IOException {
-        builder.setLength(0);
-        boolean found = false;
-
-        while(!found) {
-            if (this.bufferIn.remaining() < CHAR_SIZE) {
-                this.refillBufferIn();
-            }
-            char next = this.bufferIn.getChar();
-            builder.append(next);
-            if(next == limit) {
-                found = true;
-            }
-        }
-    }
-
-    private void refillBufferIn() throws IOException {
-        bufferIn.compact();
-        if(!hasFinished) {
-            try {
-                connection.readMore(this.bufferIn);
-                bufferIn.flip();
-            } catch (IOException ex) {
-                hasFinished = true;
-            }
-        } else {
-            throw new IOException("Done!");
-        }
-    }
-
-    public void writeNextLine(byte[] prefix, String line, byte[] suffix) throws IOException {
-        bufferOut.clear();
-        this.writeNextBlock(prefix);
-        this.writeNextBlock(line.getBytes());
-        this.writeNextBlock(suffix);
-        if (bufferOut.hasRemaining()) {
-            bufferOut.flip();
-            connection.writeMore(this.bufferOut);
-        }
-    }
-
-    private void writeNextBlock(byte[] block) throws IOException {
-        for (byte aBlock : block) {
-            if (!bufferOut.hasRemaining()) {
-                bufferOut.flip();
-                connection.writeMore(this.bufferOut);
-                bufferOut.clear();
-            }
-            bufferOut.put(aBlock);
-        }
-    }
-}
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocolParser.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocolParser.java
@@ -5,11 +5,11 @@ package nl.cwi.monetdb.mcl.protocol;
  */
 public abstract class AbstractProtocolParser {
 
-    private ServerResponses currentServerResponseHeader = ServerResponses.UNKNOWN;
+    protected ServerResponses currentServerResponseHeader = ServerResponses.UNKNOWN;
 
-    private StarterHeaders currentStarterHeader = StarterHeaders.Q_UNKNOWN;
+    protected StarterHeaders currentStarterHeader = StarterHeaders.Q_UNKNOWN;
 
-    private TableResultHeaders currentTableResultSetHeader = TableResultHeaders.UNKNOWN;
+    protected TableResultHeaders currentTableResultSetHeader = TableResultHeaders.UNKNOWN;
 
     public ServerResponses getCurrentServerResponseHeader() {
         return currentServerResponseHeader;
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java
@@ -1,7 +1,6 @@
 package nl.cwi.monetdb.mcl.protocol.newmapi;
 
 import nl.cwi.monetdb.mcl.io.SocketConnection;
-import nl.cwi.monetdb.mcl.io.SocketIOHandler;
 import nl.cwi.monetdb.mcl.protocol.AbstractProtocolParser;
 import nl.cwi.monetdb.mcl.protocol.ServerResponses;
 import nl.cwi.monetdb.mcl.protocol.StarterHeaders;
@@ -12,10 +11,10 @@ import nl.cwi.monetdb.mcl.protocol.Table
  */
 public class NewMapiProtocol extends AbstractProtocolParser {
 
-    private final SocketIOHandler handler;
+    private final SocketConnection connection;
 
     public NewMapiProtocol(SocketConnection con) {
-        this.handler = new SocketIOHandler(con);
+        this.connection = con;
     }
 
     @Override
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java
@@ -1,7 +1,6 @@
 package nl.cwi.monetdb.mcl.protocol.oldmapi;
 
 import nl.cwi.monetdb.mcl.io.SocketConnection;
-import nl.cwi.monetdb.mcl.io.SocketIOHandler;
 import nl.cwi.monetdb.mcl.protocol.AbstractProtocolParser;
 import nl.cwi.monetdb.mcl.protocol.ServerResponses;
 import nl.cwi.monetdb.mcl.protocol.StarterHeaders;
@@ -14,30 +13,59 @@ import java.io.IOException;
  */
 public class OldMapiProtocol extends AbstractProtocolParser {
 
-    private final SocketIOHandler handler;
+    private static final int STRING_BUILDER_INITIAL_SIZE = 128;
+
+    private final SocketConnection connection;
+
+    private int builderPointer;
+
+    private final StringBuilder builder = new StringBuilder(STRING_BUILDER_INITIAL_SIZE);
 
     public OldMapiProtocol(SocketConnection con) {
-        this.handler = new SocketIOHandler(con);
+        this.connection = con;
     }
 
-    public SocketIOHandler getHandler() {
-        return handler;
+    public SocketConnection getConnection() {
+        return connection;
     }
 
     @Override
     public ServerResponses getNextResponseHeaderImplementation() {
+        ServerResponses res = ServerResponses.UNKNOWN;
         try {
-            char nextToken = handler.readNextChar();
-            return OldMapiConverter.GetNextResponseOnOldMapi(nextToken);
+            while(res != ServerResponses.PROMPT) {
+                connection.readUntilChar(this.builder, '\n');
+                res = OldMapiConverter.GetNextResponseOnOldMapi(this.builder.charAt(0));
+                if(res == ServerResponses.ERROR && !this.builder.toString().matches("^![0-9A-Z]{5}!.+")) {
+                    this.builder.insert(1, "!22000!");
+                }
+            }
+            this.builderPointer = 1;
         } catch (IOException e) {
-            return ServerResponses.ERROR;
+            res = ServerResponses.ERROR;
+            this.builder.setLength(0);
+            this.builderPointer = 0;
+            this.builder.append("!22000!").append(e.getMessage());
         }
+        return res;
+    }
+
+    public String getEntireResponseLine() {
+        String res = this.builder.toString();
+        this.builderPointer = this.builder.length();
+        return res;
+    }
+
+    public String getRemainingResponseLine(int startIndex) {
+        String res = this.builder.substring(this.builderPointer + startIndex);
+        this.builderPointer = this.builder.length();
+        return res;
     }
 
     @Override
     public StarterHeaders getNextStarterHeaderImplementation() {
         try {
-            char nextToken = handler.readNextChar();
+            char nextToken = connection.readNextChar();
             return OldMapiConverter.GetNextStartHeaderOnOldMapi(nextToken);
         } catch (IOException e) {
             return StarterHeaders.Q_UNKNOWN;
@@ -48,4 +76,9 @@ public class OldMapiProtocol extends Abs
     public TableResultHeaders getNextTableHeaderImplementation() {
         return null;
     }
+
+
+    public void writeNextLine(byte[] line) throws IOException {
+        connection.writeNextLine(line);
+    }
 }
--- a/src/main/java/nl/cwi/monetdb/merovingian/Control.java
+++ b/src/main/java/nl/cwi/monetdb/merovingian/Control.java
@@ -11,7 +11,7 @@ package nl.cwi.monetdb.merovingian;
 import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
 import nl.cwi.monetdb.mcl.io.AbstractMCLWriter;
 import nl.cwi.monetdb.mcl.connection.DeleteMe;
-import nl.cwi.monetdb.mcl.MCLException;
+import nl.cwi.monetdb.mcl.connection.MCLException;
 import nl.cwi.monetdb.mcl.parser.MCLParseException;
 
 import java.io.BufferedReader;
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/AutoCommitResponse.java
@@ -0,0 +1,15 @@
+package nl.cwi.monetdb.responses;
+
+/**
+ * The AutoCommitResponse represents a transaction message.  It
+ * stores (a change in) the server side auto commit mode.<br />
+ * <tt>&amp;4 (t|f)</tt>
+ */
+public class AutoCommitResponse extends SchemaResponse {
+    public final boolean autocommit;
+
+    public AutoCommitResponse(boolean ac) {
+        // fill the blank final
+        this.autocommit = ac;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/DataBlockResponse.java
@@ -0,0 +1,123 @@
+package nl.cwi.monetdb.responses;
+
+import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
+
+import java.sql.SQLException;
+
+/**
+ * The DataBlockResponse is tabular data belonging to a
+ * ResultSetResponse.  Tabular data from the server typically looks
+ * like:
+ * <pre>
+ * [ "value",	56	]
+ * </pre>
+ * where each column is separated by ",\t" and each tuple surrounded
+ * by brackets ("[" and "]").  A DataBlockResponse object holds the
+ * raw data as read from the server, in a parsed manner, ready for
+ * easy retrieval.
+ *
+ * This object is not intended to be queried by multiple threads
+ * synchronously. It is designed to work for one thread retrieving
+ * rows from it.  When multiple threads will retrieve rows from this
+ * object, it is possible for threads to get the same data.
+ */
+public class DataBlockResponse implements IResponse {
+
+    /** The String array to keep the data in */
+    private final String[] data;
+    /** The counter which keeps the current position in the data array */
+    private int pos;
+    /** Whether we can discard lines as soon as we have read them */
+    private boolean forwardOnly;
+
+    /**
+     * Constructs a DataBlockResponse object
+     * @param size the size of the data array to create
+     * @param forward whether this is a forward only result
+     */
+    DataBlockResponse(int size, boolean forward) {
+        this.pos = -1;
+        this.data = new String[size];
+        this.forwardOnly = forward;
+    }
+
+    /**
+     * addLine adds a String of data to this object's data array.
+     * Note that an IndexOutOfBoundsException can be thrown when an
+     * attempt is made to add more than the original construction size
+     * specified.
+     *
+     * @param line the header line as String
+     * @param linetype the line type according to the MAPI protocol
+     * @return a non-null String if the line is invalid,
+     *         or additional lines are not allowed.
+     */
+    @Override
+    public String addLine(String line, int linetype) {
+        if (linetype != AbstractMCLReader.RESULT)
+            return "protocol violation: unexpected line in data block: " + line;
+        // add to the backing array
+        data[++pos] = line;
+
+        // all is well
+        return null;
+    }
+
+    /**
+     * Returns whether this Reponse expects more lines to be added
+     * to it.
+     *
+     * @return true if a next line should be added, false otherwise
+     */
+    @Override
+    public boolean wantsMore() {
+        // remember: pos is the value already stored
+        return pos + 1 < data.length;
+    }
+
+    /**
+     * Indicates that no more header lines will be added to this
+     * Response implementation.  In most cases this is a redundant
+     * operation because the data array is full.  However... it can
+     * happen that this is NOT the case!
+     *
+     * @throws SQLException if not all rows are filled
+     */
+    @Override
+    public void complete() throws SQLException {
+        if ((pos + 1) != data.length)
+            throw new SQLException("Inconsistent state detected!  Current block capacity: " + data.length +
+                    ", block usage: " + (pos + 1) + ".  Did MonetDB send what it promised to?", "M0M10");
+    }
+
+    /**
+     * Instructs the Response implementation to close and do the
+     * necessary clean up procedures.
+     *
+     */
+    @Override
+    public void close() {
+        // feed all rows to the garbage collector
+        for (int i = 0; i < data.length; i++) {
+            data[i] = null;
+        }
+    }
+
+    /**
+     * Retrieves the required row.  Warning: if the requested rows
+     * is out of bounds, an IndexOutOfBoundsException will be
+     * thrown.
+     *
+     * @param line the row to retrieve
+     * @return the requested row as String
+     */
+    String getRow(int line) {
+        if (forwardOnly) {
+            String ret = data[line];
+            data[line] = null;
+            return ret;
+        } else {
+            return data[line];
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/IResponse.java
@@ -0,0 +1,43 @@
+package nl.cwi.monetdb.responses;
+
+import java.sql.SQLException;
+
+/**
+ * A Response is a message sent by the server to indicate some
+ * action has taken place, and possible results of that action.
+ */
+public interface IResponse {
+
+    /**
+     * Adds a line to the underlying Response implementation.
+     *
+     * @param line the header line as String
+     * @param linetype the line type according to the MAPI protocol
+     * @return a non-null String if the line is invalid,
+     *         or additional lines are not allowed.
+     */
+    String addLine(String line, int linetype);
+
+    /**
+     * Returns whether this Response expects more lines to be added
+     * to it.
+     *
+     * @return true if a next line should be added, false otherwise
+     */
+    boolean wantsMore();
+
+    /**
+     * Indicates that no more header lines will be added to this
+     * Response implementation.
+     *
+     * @throws SQLException if the contents of the Response is not
+     *         consistent or sufficient.
+     */
+    void complete() throws SQLException;
+
+    /**
+     * Instructs the Response implementation to close and do the
+     * necessary clean up procedures.
+     */
+    void close();
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/ResponseList.java
@@ -0,0 +1,405 @@
+package nl.cwi.monetdb.responses;
+
+import nl.cwi.monetdb.jdbc.MonetConnection;
+import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
+import nl.cwi.monetdb.mcl.parser.MCLParseException;
+import nl.cwi.monetdb.mcl.parser.StartOfHeaderParser;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A list of Response objects.  Responses are added to this list.
+ * Methods of this class are not synchronized.  This is left as
+ * responsibility to the caller to prevent concurrent access.
+ */
+public class ResponseList {
+
+    /** the default number of rows that are (attempted to) read at once */
+    protected final static int DEF_FETCHSIZE = 250;
+    /** The sequence counter */
+    protected static int SeqCounter = 0;
+
+    /** The cache size (number of rows in a DataBlockResponse object) */
+    private final int cachesize;
+    /** The maximum number of results for this query */
+    private final int maxrows;
+    /** The ResultSet type to produce */
+    final int rstype;
+    /** The ResultSet concurrency to produce */
+    final int rsconcur;
+    /** The sequence number of this ResponseList */
+    private final int seqnr;
+    /** A list of the Responses associated with the query,
+     *  in the right order */
+    private List<IResponse> responses;
+    /** A map of ResultSetResponses, used for additional
+     *  DataBlockResponse mapping */
+    private Map<Integer, ResultSetResponse> rsresponses;
+
+    /** The current header returned by getNextResponse() */
+    private int curResponse;
+
+    /**
+     * Main constructor.  The query argument can either be a String
+     * or List.  An SQLException is thrown if another object
+     * instance is supplied.
+     *
+     * @param cachesize overall cachesize to use
+     * @param maxrows maximum number of rows to allow in the set
+     * @param rstype the type of result sets to produce
+     * @param rsconcur the concurrency of result sets to produce
+     */
+    public ResponseList(int cachesize, int maxrows, int rstype, int rsconcur) throws SQLException {
+        this.cachesize = cachesize;
+        this.maxrows = maxrows;
+        this.rstype = rstype;
+        this.rsconcur = rsconcur;
+        responses = new ArrayList<>();
+        curResponse = -1;
+        seqnr = SeqCounter++;
+    }
+
+    /**
+     * Retrieves the next available response, or null if there are
+     * no more responses.
+     *
+     * @return the next Response available or null
+     */
+    public IResponse getNextResponse() throws SQLException {
+        if (rstype == ResultSet.TYPE_FORWARD_ONLY) {
+            // free resources if we're running forward only
+            if (curResponse >= 0 && curResponse < responses.size()) {
+                IResponse tmp = responses.get(curResponse);
+                if (tmp != null) tmp.close();
+                responses.set(curResponse, null);
+            }
+        }
+        curResponse++;
+        if (curResponse >= responses.size()) {
+            // ResponseList is obviously completed so, there are no
+            // more responses
+            return null;
+        } else {
+            // return this response
+            return responses.get(curResponse);
+        }
+    }
+
+    /**
+     * Closes the Response at index i, if not null.
+     *
+     * @param i the index position of the header to close
+     */
+    public void closeResponse(int i) {
+        if (i < 0 || i >= responses.size()) return;
+        IResponse tmp = responses.set(i, null);
+        if (tmp != null)
+            tmp.close();
+    }
+
+    /**
+     * Closes the current response.
+     */
+    void closeCurrentResponse() {
+        closeResponse(curResponse);
+    }
+
+    /**
+     * Closes the current and previous responses.
+     */
+    void closeCurOldResponses() {
+        for (int i = curResponse; i >= 0; i--) {
+            closeResponse(i);
+        }
+    }
+
+    /**
+     * Closes this ResponseList by closing all the Responses in this
+     * ResponseList.
+     */
+    public void close() {
+        for (int i = 0; i < responses.size(); i++) {
+            closeResponse(i);
+        }
+    }
+
+    /**
+     * Returns whether this ResponseList has still unclosed
+     * Responses.
+     */
+    boolean hasUnclosedResponses() {
+        for (IResponse r : responses) {
+            if (r != null)
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Executes the query contained in this ResponseList, and
+     * stores the Responses resulting from this query in this
+     * ResponseList.
+     *
+     * @throws SQLException if a database error occurs
+     */
+    public void processQuery(String query) throws SQLException {
+        executeQuery(server.getQueryHeaderTemplates(), query);
+    }
+
+    /**
+     * Internal executor of queries.
+     *
+     * @param templ the template to fill in
+     * @param query the query to execute
+     * @throws SQLException if a database error occurs
+     */
+    @SuppressWarnings("fallthrough")
+    public void executeQuery(String[] templ, String query) throws SQLException {
+        boolean sendThreadInUse = false;
+        String error = null;
+
+        try {
+            synchronized (server) {
+                // make sure we're ready to send query; read data till we
+                // have the prompt it is possible (and most likely) that we
+                // already have the prompt and do not have to skip any
+                // lines.  Ignore errors from previous result sets.
+                in.waitForPrompt();
+
+                // {{{ set reply size
+                /**
+                 * Change the reply size of the server.  If the given
+                 * value is the same as the current value known to use,
+                 * then ignore this call.  If it is set to 0 we get a
+                 * prompt after the server sent it's header.
+                 */
+                int size = cachesize == 0 ? DEF_FETCHSIZE : cachesize;
+                size = maxrows != 0 ? Math.min(maxrows, size) : size;
+                // don't do work if it's not needed
+                if (server.getLang() == MonetConnection.LANG_SQL && size != curReplySize && templ != server.getCommandHeaderTemplates()) {
+                    sendControlCommand("reply_size " + size);
+
+                    // store the reply size after a successful change
+                    curReplySize = size;
+                }
+                // }}} set reply size
+
+                // If the query is larger than the TCP buffer size, use a
+                // special send thread to avoid deadlock with the server due
+                // to blocking behaviour when the buffer is full.  Because
+                // the server will be writing back results to us, it will
+                // eventually block as well when its TCP buffer gets full,
+                // as we are blocking an not consuming from it.  The result
+                // is a state where both client and server want to write,
+                // but block.
+                if (query.length() > server.getBlockSize()) {
+                    // get a reference to the send thread
+                    if (sendThread == null)
+                        sendThread = new SendThread(out);
+                    // tell it to do some work!
+                    sendThread.runQuery(templ, query);
+                    sendThreadInUse = true;
+                } else {
+                    // this is a simple call, which is a lot cheaper and will
+                    // always succeed for small queries.
+                    out.writeLine((templ[0] == null ? "" : templ[0] + query + templ[1] == null ? "" : templ[1]));
+                }
+
+                // go for new results
+                String tmpLine = in.readLine();
+                int linetype = in.getLineType();
+                IResponse res = null;
+                while (linetype != AbstractMCLReader.PROMPT) {
+                    // each response should start with a start of header
+                    // (or error)
+                    switch (linetype) {
+                        case AbstractMCLReader.SOHEADER:
+                            // make the response object, and fill it
+                            try {
+                                switch (sohp.parse(tmpLine)) {
+                                    case StartOfHeaderParser.Q_PARSE:
+                                        throw new MCLParseException("Q_PARSE header not allowed here", 1);
+                                    case StartOfHeaderParser.Q_TABLE:
+                                    case StartOfHeaderParser.Q_PREPARE: {
+                                        int id = sohp.getNextAsInt();
+                                        int tuplecount = sohp.getNextAsInt();
+                                        int columncount = sohp.getNextAsInt();
+                                        int rowcount = sohp.getNextAsInt();
+                                        // enforce the maxrows setting
+                                        if (maxrows != 0 && tuplecount > maxrows)
+                                            tuplecount = maxrows;
+                                        res = new ResultSetResponse(id, tuplecount, columncount, rowcount,
+                                                this, seqnr);
+                                        // only add this resultset to
+                                        // the hashmap if it can possibly
+                                        // have an additional datablock
+                                        if (rowcount < tuplecount) {
+                                            if (rsresponses == null)
+                                                rsresponses = new HashMap<>();
+                                            rsresponses.put(id, (ResultSetResponse) res);
+                                        }
+                                    } break;
+                                    case StartOfHeaderParser.Q_UPDATE:
+                                        res = new UpdateResponse(
+                                                sohp.getNextAsInt(),   // count
+                                                sohp.getNextAsString() // key-id
+                                        );
+                                        break;
+                                    case StartOfHeaderParser.Q_SCHEMA:
+                                        res = new SchemaResponse();
+                                        break;
+                                    case StartOfHeaderParser.Q_TRANS:
+                                        boolean ac = sohp.getNextAsString().equals("t");
+                                        if (autoCommit && ac) {
+                                            addWarning("Server enabled auto commit " +
+                                                    "mode while local state " +
+                                                    "already was auto commit.", "01M11"
+                                            );
+                                        }
+                                        autoCommit = ac;
+                                        res = new AutoCommitResponse(ac);
+                                        break;
+                                    case StartOfHeaderParser.Q_BLOCK: {
+                                        // a new block of results for a
+                                        // response...
+                                        int id = sohp.getNextAsInt();
+                                        sohp.getNextAsInt();	// columncount
+                                        int rowcount = sohp.getNextAsInt();
+                                        int offset = sohp.getNextAsInt();
+                                        ResultSetResponse t = rsresponses.get(id);
+                                        if (t == null) {
+                                            error = "M0M12!no ResultSetResponse with id " + id + " found";
+                                            break;
+                                        }
+
+                                        DataBlockResponse r = new DataBlockResponse(rowcount,
+                                                t.getRSType() == ResultSet.TYPE_FORWARD_ONLY);
+
+                                        t.addDataBlockResponse(offset, r);
+                                        res = r;
+                                    } break;
+                                }
+                            } catch (MCLParseException e) {
+                                error = "M0M10!error while parsing start of header:\n" +
+                                        e.getMessage() +
+                                        " found: '" + tmpLine.charAt(e.getErrorOffset()) + "'" +
+                                        " in: \"" + tmpLine + "\"" +
+                                        " at pos: " + e.getErrorOffset();
+                                // flush all the rest
+                                in.waitForPrompt();
+                                linetype = in.getLineType();
+                                break;
+                            }
+
+                            // immediately handle errors after parsing
+                            // the header (res may be null)
+                            if (error != null) {
+                                in.waitForPrompt();
+                                linetype = in.getLineType();
+                                break;
+                            }
+
+                            // here we have a res object, which
+                            // we can start filling
+                            while (res.wantsMore()) {
+                                error = res.addLine(
+                                        in.readLine(),
+                                        in.getLineType()
+                                );
+                                if (error != null) {
+                                    // right, some protocol violation,
+                                    // skip the rest of the result
+                                    error = "M0M10!" + error;
+                                    in.waitForPrompt();
+                                    linetype = in.getLineType();
+                                    break;
+                                }
+                            }
+                            if (error != null)
+                                break;
+                            // it is of no use to store
+                            // DataBlockReponses, you never want to
+                            // retrieve them directly anyway
+                            if (!(res instanceof DataBlockResponse))
+                                responses.add(res);
+
+                            // read the next line (can be prompt, new
+                            // result, error, etc.) before we start the
+                            // loop over
+                            tmpLine = in.readLine();
+                            linetype = in.getLineType();
+                            break;
+                        case AbstractMCLReader.INFO:
+                            addWarning(tmpLine.substring(1), "01000");
+
+                            // read the next line (can be prompt, new
+                            // result, error, etc.) before we start the
+                            // loop over
+                            tmpLine = in.readLine();
+                            linetype = in.getLineType();
+                            break;
+                        default:	// Yeah... in Java this is correct!
+                            // we have something we don't
+                            // expect/understand, let's make it an error
+                            // message
+                            tmpLine = "!M0M10!protocol violation, unexpected line: " + tmpLine;
+                            // don't break; fall through...
+                        case AbstractMCLReader.ERROR:
+                            // read everything till the prompt (should be
+                            // error) we don't know if we ignore some
+                            // garbage here... but the log should reveal
+                            // that
+                            error = in.waitForPrompt();
+                            linetype = in.getLineType();
+                            if (error != null) {
+                                error = tmpLine.substring(1) + "\n" + error;
+                            } else {
+                                error = tmpLine.substring(1);
+                            }
+                            break;
+                    }
+                }
+            }
+
+            // if we used the sendThread, make sure it has finished
+            if (sendThreadInUse) {
+                String tmp = sendThread.getErrors();
+                if (tmp != null) {
+                    if (error == null) {
+                        error = "08000!" + tmp;
+                    } else {
+                        error += "\n08000!" + tmp;
+                    }
+                }
+            }
+            if (error != null) {
+                SQLException ret = null;
+                String[] errors = error.split("\n");
+                for (String error1 : errors) {
+                    if (ret == null) {
+                        ret = new SQLException(error1.substring(6),
+                                error1.substring(0, 5));
+                    } else {
+                        ret.setNextException(new SQLException(
+                                error1.substring(6),
+                                error1.substring(0, 5)));
+                    }
+                }
+                throw ret;
+            }
+        } catch (SocketTimeoutException e) {
+            close(); // JDBC 4.1 semantics, abort()
+            throw new SQLException("connection timed out", "08M33");
+        } catch (IOException e) {
+            closed = true;
+            throw new SQLException(e.getMessage() + " (mserver still alive?)", "08000");
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/ResultSetResponse.java
@@ -0,0 +1,409 @@
+package nl.cwi.monetdb.responses;
+
+import nl.cwi.monetdb.jdbc.MonetConnection;
+import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
+import nl.cwi.monetdb.mcl.parser.HeaderLineParser;
+import nl.cwi.monetdb.mcl.parser.MCLParseException;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * The ResultSetResponse represents a tabular result sent by the
+ * server.  This is typically an SQL table.  The MAPI headers of the
+ * Response look like:
+ * <pre>
+ * &amp;1 1 28 2 10
+ * # name,     value # name
+ * # varchar,  varchar # type
+ * </pre>
+ * there the first line consists out of<br />
+ * <tt>&amp;"qt" "id" "tc" "cc" "rc"</tt>.
+ */
+public class ResultSetResponse implements IResponse {
+    /** The number of columns in this result */
+    public final int columncount;
+    /** The total number of rows this result set has */
+    public final int tuplecount;
+    /** The numbers of rows to retrieve per DataBlockResponse */
+    private int cacheSize;
+    /** The table ID of this result */
+    public final int id;
+    /** The names of the columns in this result */
+    private String[] name;
+    /** The types of the columns in this result */
+    private String[] type;
+    /** The max string length for each column in this result */
+    private int[] columnLengths;
+    /** The table for each column in this result */
+    private String[] tableNames;
+    /** The query sequence number */
+    private final int seqnr;
+    /** A List of result blocks (chunks of size fetchSize/cacheSize) */
+    private DataBlockResponse[] resultBlocks;
+
+    /** A bitmap telling whether the headers are set or not */
+    private boolean[] isSet;
+    /** Whether this Response is closed */
+    private boolean closed;
+
+    /** The Connection that we should use when requesting a new block */
+    private ResponseList parent;
+    /** Whether the fetchSize was explitly set by the user */
+    private boolean cacheSizeSetExplicitly = false;
+    /** Whether we should send an Xclose command to the server
+     *  if we close this Response */
+    private boolean destroyOnClose;
+    /** the offset to be used on Xexport queries */
+    private int blockOffset = 0;
+
+    /** A parser for header lines */
+    HeaderLineParser hlp;
+
+    private final static int NAMES	= 0;
+    private final static int TYPES	= 1;
+    private final static int TABLES	= 2;
+    private final static int LENS	= 3;
+
+    /**
+     * Sole constructor, which requires a MonetConnection parent to
+     * be given.
+     *
+     * @param id the ID of the result set
+     * @param tuplecount the total number of tuples in the result set
+     * @param columncount the number of columns in the result set
+     * @param rowcount the number of rows in the current block
+     * @param parent the parent that created this Response and will
+     *               supply new result blocks when necessary
+     * @param seq the query sequence number
+     */
+    ResultSetResponse(int id, int tuplecount, int columncount, int rowcount, ResponseList parent, int seq) throws SQLException {
+        isSet = new boolean[7];
+        this.parent = parent;
+        if (parent.cachesize == 0) {
+				/* Below we have to calculate how many "chunks" we need
+				 * to allocate to store the entire result.  However, if
+				 * the user didn't set a cache size, as in this case, we
+				 * need to stick to our defaults. */
+            cacheSize = ResponseList.DEF_FETCHSIZE;
+            cacheSizeSetExplicitly = false;
+        } else {
+            cacheSize = parent.cachesize;
+            cacheSizeSetExplicitly = true;
+        }
+			/* So far, so good.  Now the problem with EXPLAIN, DOT, etc
+			 * queries is, that they don't support any block fetching,
+			 * so we need to always fetch everything at once.  For that
+			 * reason, the cache size is here set to the rowcount if
+			 * it's larger, such that we do a full fetch at once.
+			 * (Because we always set a reply_size, we can only get a
+			 * larger rowcount from the server if it doesn't paginate,
+			 * because it's a pseudo SQL result.) */
+        if (rowcount > cacheSize)
+            cacheSize = rowcount;
+        seqnr = seq;
+        closed = false;
+        destroyOnClose = id > 0 && tuplecount > rowcount;
+
+        this.id = id;
+        this.tuplecount = tuplecount;
+        this.columncount = columncount;
+        this.resultBlocks = new DataBlockResponse[(tuplecount / cacheSize) + 1];
+
+        hlp = server.getHeaderLineParser(columncount);
+
+        resultBlocks[0] = new DataBlockResponse(rowcount, parent.rstype == ResultSet.TYPE_FORWARD_ONLY);
+    }
+
+    /**
+     * Parses the given string and changes the value of the matching
+     * header appropriately, or passes it on to the underlying
+     * DataResponse.
+     *
+     * @param tmpLine the string that contains the header
+     * @return a non-null String if the header cannot be parsed or
+     *         is unknown
+     */
+    // {{{ addLine
+    @Override
+    public String addLine(String tmpLine, int linetype) {
+        if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) {
+            return resultBlocks[0].addLine(tmpLine, linetype);
+        }
+
+        if (linetype != AbstractMCLReader.HEADER)
+            return "header expected, got: " + tmpLine;
+
+        // depending on the name of the header, we continue
+        try {
+            switch (hlp.parse(tmpLine)) {
+                case HeaderLineParser.NAME:
+                    name = hlp.values.clone();
+                    isSet[NAMES] = true;
+                    break;
+                case HeaderLineParser.LENGTH:
+                    columnLengths = hlp.intValues.clone();
+                    isSet[LENS] = true;
+                    break;
+                case HeaderLineParser.TYPE:
+                    type = hlp.values.clone();
+                    isSet[TYPES] = true;
+                    break;
+                case HeaderLineParser.TABLE:
+                    tableNames = hlp.values.clone();
+                    isSet[TABLES] = true;
+                    break;
+            }
+        } catch (MCLParseException e) {
+            return e.getMessage();
+        }
+
+        // all is well
+        return null;
+    }
+    // }}}
+
+    /**
+     * Returns whether this ResultSetResponse needs more lines.
+     * This method returns true if not all headers are set, or the
+     * first DataBlockResponse reports to want more.
+     */
+    @Override
+    public boolean wantsMore() {
+        return !(isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) || resultBlocks[0].wantsMore();
+    }
+
+    /**
+     * Returns an array of Strings containing the values between
+     * ',\t' separators.
+     *
+     * @param chrLine a character array holding the input data
+     * @param start where the relevant data starts
+     * @param stop where the relevant data stops
+     * @return an array of Strings
+     */
+    final private String[] getValues(char[] chrLine, int start, int stop) {
+        int elem = 0;
+        String[] values = new String[columncount];
+
+        for (int i = start; i < stop; i++) {
+            if (chrLine[i] == '\t' && chrLine[i - 1] == ',') {
+                values[elem++] =
+                        new String(chrLine, start, i - 1 - start);
+                start = i + 1;
+            }
+        }
+        // at the left over part
+        values[elem++] = new String(chrLine, start, stop - start);
+
+        return values;
+    }
+
+    /**
+     * Adds the given DataBlockResponse to this ResultSetResponse at
+     * the given block position.
+     *
+     * @param offset the offset number of rows for this block
+     * @param rr the DataBlockResponse to add
+     */
+    void addDataBlockResponse(int offset, DataBlockResponse rr) {
+        int block = (offset - blockOffset) / cacheSize;
+        resultBlocks[block] = rr;
+    }
+
+    /**
+     * Marks this Response as being completed.  A complete Response
+     * needs to be consistent with regard to its internal data.
+     *
+     * @throws SQLException if the data currently in this Response is not
+     *                      sufficient to be consistant
+     */
+    @Override
+    public void complete() throws SQLException {
+        String error = "";
+        if (!isSet[NAMES]) error += "name header missing\n";
+        if (!isSet[TYPES]) error += "type header missing\n";
+        if (!isSet[TABLES]) error += "table name header missing\n";
+        if (!isSet[LENS]) error += "column width header missing\n";
+        if (!error.equals("")) throw new SQLException(error, "M0M10");
+    }
+
+    /**
+     * Returns the names of the columns
+     *
+     * @return the names of the columns
+     */
+    String[] getNames() {
+        return name;
+    }
+
+    /**
+     * Returns the types of the columns
+     *
+     * @return the types of the columns
+     */
+    String[] getTypes() {
+        return type;
+    }
+
+    /**
+     * Returns the tables of the columns
+     *
+     * @return the tables of the columns
+     */
+    String[] getTableNames() {
+        return tableNames;
+    }
+
+    /**
+     * Returns the lengths of the columns
+     *
+     * @return the lengths of the columns
+     */
+    int[] getColumnLengths() {
+        return columnLengths;
+    }
+
+    /**
+     * Returns the cache size used within this Response
+     *
+     * @return the cache size
+     */
+    int getCacheSize() {
+        return cacheSize;
+    }
+
+    /**
+     * Returns the current block offset
+     *
+     * @return the current block offset
+     */
+    int getBlockOffset() {
+        return blockOffset;
+    }
+
+    /**
+     * Returns the ResultSet type, FORWARD_ONLY or not.
+     *
+     * @return the ResultSet type
+     */
+    int getRSType() {
+        return parent.rstype;
+    }
+
+    /**
+     * Returns the concurrency of the ResultSet.
+     *
+     * @return the ResultSet concurrency
+     */
+    int getRSConcur() {
+        return parent.rsconcur;
+    }
+
+    /**
+     * Returns a line from the cache. If the line is already present in the
+     * cache, it is returned, if not apropriate actions are taken to make
+     * sure the right block is being fetched and as soon as the requested
+     * line is fetched it is returned.
+     *
+     * @param row the row in the result set to return
+     * @return the exact row read as requested or null if the requested row
+     *         is out of the scope of the result set
+     * @throws SQLException if an database error occurs
+     */
+    String getLine(int row) throws SQLException {
+        if (row >= tuplecount || row < 0)
+            return null;
+
+        int block = (row - blockOffset) / cacheSize;
+        int blockLine = (row - blockOffset) % cacheSize;
+
+        // do we have the right block loaded? (optimistic try)
+        DataBlockResponse rawr;
+        // load block if appropriate
+        if ((rawr = resultBlocks[block]) == null) {
+            /// TODO: ponder about a maximum number of blocks to keep
+            ///       in memory when dealing with random access to
+            ///       reduce memory blow-up
+
+            // if we're running forward only, we can discard the oldmapi
+            // block loaded
+            if (parent.rstype == ResultSet.TYPE_FORWARD_ONLY) {
+                for (int i = 0; i < block; i++)
+                    resultBlocks[i] = null;
+
+                if (ResponseList.SeqCounter - 1 == seqnr && !cacheSizeSetExplicitly &&
+                        tuplecount - row > cacheSize && cacheSize < ResponseList.DEF_FETCHSIZE * 10) {
+                    // there has no query been issued after this
+                    // one, so we can consider this an uninterrupted
+                    // continuation request.  Let's once increase
+                    // the cacheSize as it was not explicitly set,
+                    // since the chances are high that we won't
+                    // bother anyone else by doing so, and just
+                    // gaining some performance.
+
+                    // store the previous position in the
+                    // blockOffset variable
+                    blockOffset += cacheSize;
+
+                    // increase the cache size (a lot)
+                    cacheSize *= 10;
+
+                    // by changing the cacheSize, we also
+                    // change the block measures.  Luckily
+                    // we don't care about previous blocks
+                    // because we have a forward running
+                    // pointer only.  However, we do have
+                    // to recalculate the block number, to
+                    // ensure the next call to find this
+                    // new block.
+                    block = (row - blockOffset) / cacheSize;
+                    blockLine = (row - blockOffset) % cacheSize;
+                }
+            }
+
+            // ok, need to fetch cache block first
+            parent.executeQuery(server.getCommandHeaderTemplates(),
+                    "export " + id + " " + ((block * cacheSize) + blockOffset) + " " + cacheSize);
+            rawr = resultBlocks[block];
+            if (rawr == null) throw
+                    new AssertionError("block " + block + " should have been fetched by now :(");
+        }
+
+        return rawr.getRow(blockLine);
+    }
+
+    /**
+     * Closes this Response by sending an Xclose to the server indicating
+     * that the result can be closed at the server side as well.
+     */
+    @Override
+    public void close() {
+        if (closed) return;
+        // send command to server indicating we're done with this
+        // result only if we had an ID in the header and this result
+        // was larger than the reply size
+        try {
+            if (destroyOnClose) sendControlCommand("close " + id);
+        } catch (SQLException e) {
+            // probably a connection error...
+        }
+
+        // close the data block associated with us
+        for (int i = 1; i < resultBlocks.length; i++) {
+            DataBlockResponse r = resultBlocks[i];
+            if (r != null) r.close();
+        }
+
+        closed = true;
+    }
+
+    /**
+     * Returns whether this Response is closed
+     *
+     * @return whether this Response is closed
+     */
+    boolean isClosed() {
+        return closed;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/SchemaResponse.java
@@ -0,0 +1,37 @@
+package nl.cwi.monetdb.responses;
+
+import java.sql.Statement;
+
+/**
+ * The SchemaResponse represents an schema modification response.
+ * It is issued on statements like CREATE, DROP or ALTER TABLE.
+ * This response keeps a field that represents the success state, as
+ * defined by JDBC, which is currently in MonetDB's case always
+ * SUCCESS_NO_INFO.  Note that this state is not sent by the
+ * server.<br />
+ * <tt>&amp;3</tt>
+ */
+public class SchemaResponse implements IResponse {
+
+    public final int state = Statement.SUCCESS_NO_INFO;
+
+    @Override
+    public String addLine(String line, int linetype) {
+        return "Header lines are not supported for a SchemaResponse";
+    }
+
+    @Override
+    public boolean wantsMore() {
+        return false;
+    }
+
+    @Override
+    public void complete() {
+        // empty, because there is nothing to check
+    }
+
+    @Override
+    public void close() {
+        // nothing to do here...
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/SendThread.java
@@ -0,0 +1,145 @@
+package nl.cwi.monetdb.responses;
+
+import nl.cwi.monetdb.mcl.io.AbstractMCLWriter;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A thread to send a query to the server.  When sending large
+ * amounts of data to a server, the output buffer of the underlying
+ * communication socket may overflow.  In such case the sending
+ * process blocks.  In order to prevent deadlock, it might be
+ * desirable that the driver as a whole does not block.  This thread
+ * facilitates the prevention of such 'full block', because this
+ * separate thread only will block.<br />
+ * This thread is designed for reuse, as thread creation costs are
+ * high.
+ */
+public class SendThread extends Thread {
+    /** The state WAIT represents this thread to be waiting for
+     *  something to do */
+    private final static int WAIT = 0;
+    /** The state QUERY represents this thread to be executing a query */
+    private final static int QUERY = 1;
+    /** The state SHUTDOWN is the final state that ends this thread */
+    private final static int SHUTDOWN = -1;
+
+    private String[] templ;
+    private String query;
+    private AbstractMCLWriter out;
+    private String error;
+    private int state = WAIT;
+
+    private final Lock sendLock = new ReentrantLock();
+    private final Condition queryAvailable = sendLock.newCondition();
+    private final Condition waiting = sendLock.newCondition();
+
+    /**
+     * Constructor which immediately starts this thread and sets it
+     * into daemon mode.
+     *
+     * @param out the socket to write to
+     */
+    public SendThread(AbstractMCLWriter out) {
+        super("SendThread");
+        this.setDaemon(true);
+        this.out = out;
+        this.start();
+    }
+
+    @Override
+    public void run() {
+        sendLock.lock();
+        try {
+            while (true) {
+                while (state == WAIT) {
+                    try {
+                        queryAvailable.await();
+                    } catch (InterruptedException e) {
+                        // woken up, eh?
+                    }
+                }
+                if (state == SHUTDOWN)
+                    break;
+
+                // state is QUERY here
+                try {
+                    out.writeLine((templ[0] == null ? "" : templ[0]) + query + (templ[1] == null ? "" : templ[1]));
+                } catch (IOException e) {
+                    error = e.getMessage();
+                }
+
+                // update our state, and notify, maybe someone is waiting
+                // for us in throwErrors
+                state = WAIT;
+                waiting.signal();
+            }
+        } finally {
+            sendLock.unlock();
+        }
+    }
+
+    /**
+     * Starts sending the given query over the given socket.  Beware
+     * that the thread should be finished (can be assured by calling
+     * throwErrors()) before this method is called!
+     *
+     * @param templ the query template
+     * @param query the query itself
+     * @throws SQLException if this SendThread is already in use
+     */
+    public void runQuery(String[] templ, String query) throws SQLException {
+        sendLock.lock();
+        try {
+            if (state != WAIT)
+                throw new SQLException("SendThread already in use or shutting down!", "M0M03");
+
+            this.templ = templ;
+            this.query = query;
+
+            // let the thread know there is some work to do
+            state = QUERY;
+            queryAvailable.signal();
+        } finally {
+            sendLock.unlock();
+        }
+    }
+
+    /**
+     * Returns errors encountered during the sending process.
+     *
+     * @return the errors or null if none
+     */
+    public String getErrors() {
+        sendLock.lock();
+        try {
+            // make sure the thread is in WAIT state, not QUERY
+            while (state == QUERY) {
+                try {
+                    waiting.await();
+                } catch (InterruptedException e) {
+                    // just try again
+                }
+            }
+            if (state == SHUTDOWN)
+                error = "SendThread is shutting down";
+        } finally {
+            sendLock.unlock();
+        }
+        return error;
+    }
+
+    /**
+     * Requests this SendThread to stop.
+     */
+    public void shutdown() {
+        sendLock.lock();
+        state = SHUTDOWN;
+        sendLock.unlock();
+        this.interrupt();  // break any wait conditions
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/responses/UpdateResponse.java
@@ -0,0 +1,40 @@
+package nl.cwi.monetdb.responses;
+
+/**
+ * The UpdateResponse represents an update statement response.  It
+ * is issued on an UPDATE, INSERT or DELETE SQL statement.  This
+ * response keeps a count field that represents the affected rows
+ * and a field that contains the last inserted auto-generated ID, or
+ * -1 if not applicable.<br />
+ * <tt>&amp;2 0 -1</tt>
+ */
+public class UpdateResponse implements IResponse {
+    public final int count;
+    public final String lastid;
+
+    public UpdateResponse(int cnt, String id) {
+        // fill the blank finals
+        this.count = cnt;
+        this.lastid = id;
+    }
+
+    @Override
+    public String addLine(String line, int linetype) {
+        return "Header lines are not supported for an UpdateResponse";
+    }
+
+    @Override
+    public boolean wantsMore() {
+        return false;
+    }
+
+    @Override
+    public void complete() {
+        // empty, because there is nothing to check
+    }
+
+    @Override
+    public void close() {
+        // nothing to do here...
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/util/ChannelSecurity.java
@@ -0,0 +1,43 @@
+package nl.cwi.monetdb.util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Created by ferreira on 12/1/16.
+ */
+public class ChannelSecurity {
+
+    private static char HexChar(int n) { return (n > 9) ? (char) ('a' + (n - 10)) : (char) ('0' + n); }
+
+    /**
+     * Small helper method to convert a byte string to a hexadecimal
+     * string representation.
+     *
+     * @param digest the byte array to convert
+     * @return the byte array as hexadecimal string
+     */
+    private static String ToHex(byte[] digest) {
+        char[] result = new char[digest.length * 2];
+        int pos = 0;
+        for (byte aDigest : digest) {
+            result[pos++] = HexChar((aDigest & 0xf0) >> 4);
+            result[pos++] = HexChar(aDigest & 0x0f);
+        }
+        return new String(result);
+    }
+
+    public static String DigestStrings(String algorithm, String... toDigests) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(algorithm);
+            for (String str : toDigests) {
+                md.update(str.getBytes("UTF-8"));
+            }
+            byte[] digest = md.digest();
+            return ToHex(digest);
+        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+            throw new AssertionError("internal error: " + e.toString());
+        }
+    }
+}
--- a/src/main/java/nl/cwi/monetdb/util/SQLRestore.java
+++ b/src/main/java/nl/cwi/monetdb/util/SQLRestore.java
@@ -14,7 +14,7 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import nl.cwi.monetdb.mcl.MCLException;
+import nl.cwi.monetdb.mcl.connection.MCLException;
 import nl.cwi.monetdb.mcl.io.AbstractMCLReader;
 import nl.cwi.monetdb.mcl.io.AbstractMCLWriter;
 import nl.cwi.monetdb.mcl.connection.DeleteMe;