diff src/main/java/nl/cwi/monetdb/jdbc/MonetDatabaseMetaData.java @ 0:a5a898f6886c

Copy of MonetDB java directory changeset e6e32756ad31.
author Sjoerd Mullender <sjoerd@acm.org>
date Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21)
parents
children b3ca1157be73
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -0,0 +1,4086 @@
+/*
+ * 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.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Statement;
+import java.sql.SQLException;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowIdLifetime;
+import java.sql.Types;
+
+import java.util.ArrayList;
+
+/**
+ * A DatabaseMetaData object suitable for the MonetDB database.
+ *
+ * @author Fabian Groffen, Martin van Dinther
+ * @version 0.6
+ */
+public class MonetDatabaseMetaData extends MonetWrapper implements DatabaseMetaData {
+	private Connection con;
+	private String env_current_user;
+	private String env_monet_version;
+	private String env_max_clients;
+
+	public MonetDatabaseMetaData(Connection parent) {
+		con = parent;
+	}
+
+	/**
+	 * Internal cache for 3 environment values retrieved from the
+	 * server, to avoid querying the server over and over again.
+	 * Once a value is read, it is kept in the private env_* variables for reuse.
+	 * We currently only need the env values of: current_user, monet_version and max_clients.
+	 */
+	private synchronized void getEnvValues() {
+		Statement st = null;
+		ResultSet rs = null;
+		try {
+			st = con.createStatement();
+			rs = st.executeQuery(
+				"SELECT \"name\", \"value\" FROM \"sys\".\"environment\"" +
+				" WHERE \"name\" IN ('monet_version', 'max_clients')" +
+				" UNION SELECT 'current_user' as \"name\", current_user as \"value\"");
+			if (rs != null) {
+				while (rs.next()) {
+					String prop = rs.getString("name");
+					String value = rs.getString("value");
+					if ("current_user".equals(prop)) {
+						env_current_user = value;
+					} else
+					if ("monet_version".equals(prop)) {
+						env_monet_version = value;
+					} else
+					if ("max_clients".equals(prop)) {
+						env_max_clients = value;
+					}
+				}
+			}
+		} catch (SQLException e) {
+			// ignore
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+			if (st != null) {
+				try {
+					 st.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+		}
+// for debug: System.out.println("Read: env_current_user: " + env_current_user + "  env_monet_version: " + env_monet_version + "  env_max_clients: " + env_max_clients);
+	}
+
+
+	/**
+	 * Internal utility method to create a Statement object, execute a query and return the ResulSet object.
+	 * As the Statement object is created internally (the caller does not see it and thus can not close it),
+	 * we set it to close (and free server resources) when the ResultSet object is closed by the caller.
+	 */
+	private ResultSet executeMetaDataQuery(String query) throws SQLException {
+		Statement stmt = null;
+		ResultSet rs = null;
+		stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
+		if (stmt != null) {
+// for debug: System.out.println("SQL (len " + query.length() + "): " + query);
+			rs = stmt.executeQuery(query);
+			if (rs != null) {
+				/* we want the statement object to be closed also when the resultset is closed by the caller */
+				stmt.closeOnCompletion();
+			} else {
+				/* failed to produce a resultset, so release resources for created statement object now */
+				stmt.close();
+			}
+		}
+		return rs;
+	}
+
+	/**
+	 * Can all the procedures returned by getProcedures be called
+	 * by the current user?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean allProceduresAreCallable() {
+		return true;
+	}
+
+	/**
+	 * Can all the tables returned by getTable be SELECTed by
+	 * the current user?
+	 *
+	 * @return true because we only have one user a.t.m.
+	 */
+	@Override
+	public boolean allTablesAreSelectable() {
+		return true;
+	}
+
+	/**
+	 * What is the URL for this database?
+	 *
+	 * @return a reconstructed connection string
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public String getURL() throws SQLException {
+		return ((MonetConnection)con).getJDBCURL();
+	}
+
+	/**
+	 * What is our user name as known to the database?
+	 *
+	 * @return sql user
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public String getUserName() throws SQLException {
+		if (env_current_user == null)
+			getEnvValues();
+		return env_current_user;
+	}
+
+	/**
+	 * Is the database in read-only mode?
+	 *
+	 * @return always false for now
+	 */
+	@Override
+	public boolean isReadOnly() {
+		return false;
+	}
+
+	/**
+	 * Are NULL values sorted high?
+	 *
+	 * @return true because MonetDB puts NULL values on top upon ORDER BY
+	 */
+	@Override
+	public boolean nullsAreSortedHigh() {
+		return true;
+	}
+
+	/**
+	 * Are NULL values sorted low?
+	 *
+	 * @return negative of nullsAreSortedHigh()
+	 * @see #nullsAreSortedHigh()
+	 */
+	@Override
+	public boolean nullsAreSortedLow() {
+		return !nullsAreSortedHigh();
+	}
+
+	/**
+	 * Are NULL values sorted at the start regardless of sort order?
+	 *
+	 * @return false, since MonetDB doesn't do this
+	 */
+	@Override
+	public boolean nullsAreSortedAtStart() {
+		return false;
+	}
+
+	/**
+	 * Are NULL values sorted at the end regardless of sort order?
+	 *
+	 * @return false, since MonetDB doesn't do this
+	 */
+	@Override
+	public boolean nullsAreSortedAtEnd() {
+		return false;
+	}
+
+	/**
+	 * What is the name of this database product - this should be MonetDB
+	 * of course, so we return that explicitly.
+	 *
+	 * @return the database product name
+	 */
+	@Override
+	public String getDatabaseProductName() {
+		return "MonetDB";
+	}
+
+	/**
+	 * What is the version of this database product.
+	 *
+	 * @return the mserver5 version number string
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public String getDatabaseProductVersion() throws SQLException {
+		if (env_monet_version == null)
+			getEnvValues();
+		return env_monet_version;
+	}
+
+	/**
+	 * What is the name of this JDBC driver?
+	 *
+	 * @return the JDBC driver name
+	 */
+	@Override
+	public String getDriverName() {
+		return "MonetDB Native Driver";
+	}
+
+	/**
+	 * Retrieves the version number of this JDBC driver as a String.
+	 *
+	 * @return the JDBC driver version string
+	 */
+	@Override
+	public String getDriverVersion() {
+		return MonetDriver.getDriverVersion();
+	}
+
+	/**
+	 * What is this JDBC driver's major version number?
+	 *
+	 * @return the JDBC driver major version number
+	 */
+	@Override
+	public int getDriverMajorVersion() {
+		return MonetDriver.getDriverMajorVersion();
+	}
+
+	/**
+	 * What is this JDBC driver's minor version number?
+	 *
+	 * @return the JDBC driver minor version number
+	 */
+	@Override
+	public int getDriverMinorVersion() {
+		return MonetDriver.getDriverMinorVersion();
+	}
+
+	/**
+	 * Does the database store tables in a local file?	No - it
+	 * stores them in a file on the server.
+	 *
+	 * @return false because that's what MonetDB is for
+	 */
+	@Override
+	public boolean usesLocalFiles() {
+		return false;
+	}
+
+	/**
+	 * Does the database use a local file for each table?  Well, not really,
+	 * since it doesn't use local files.
+	 *
+	 * @return false for it doesn't
+	 */
+	@Override
+	public boolean usesLocalFilePerTable() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case unquoted SQL identifiers
+	 * as case sensitive and as a result store them in mixed case?
+	 * A JDBC-Compliant driver will always return false.
+	 *
+	 * @return false
+	 */
+	@Override
+	public boolean supportsMixedCaseIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case unquoted SQL identifiers as
+	 * case insensitive and store them in upper case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesUpperCaseIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case unquoted SQL identifiers as
+	 * case insensitive and store them in lower case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesLowerCaseIdentifiers() {
+		return true;
+	}
+
+	/**
+	 * Does the database treat mixed case unquoted SQL identifiers as
+	 * case insensitive and store them in mixed case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesMixedCaseIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case quoted SQL identifiers as
+	 * case sensitive and as a result store them in mixed case?  A
+	 * JDBC compliant driver will always return true.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsMixedCaseQuotedIdentifiers() {
+		return true;
+	}
+
+	/**
+	 * Does the database treat mixed case quoted SQL identifiers as
+	 * case insensitive and store them in upper case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesUpperCaseQuotedIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case quoted SQL identifiers as case
+	 * insensitive and store them in lower case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesLowerCaseQuotedIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * Does the database treat mixed case quoted SQL identifiers as case
+	 * insensitive and store them in mixed case?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean storesMixedCaseQuotedIdentifiers() {
+		return false;
+	}
+
+	/**
+	 * What is the string used to quote SQL identifiers?  This returns
+	 * a space if identifier quoting isn't supported.  A JDBC Compliant
+	 * driver will always use a double quote character.
+	 *
+	 * @return the quoting string
+	 */
+	@Override
+	public String getIdentifierQuoteString() {
+		return "\"";
+	}
+
+	/**
+	 * Get a comma separated list of all a database's SQL keywords that
+	 * are NOT also SQL:2003 keywords.
+	 *
+	 * @return a comma separated list of MonetDB keywords
+	 */
+	@Override
+	public String getSQLKeywords() {
+		String keywords = getConcatenatedStringFromQuery("SELECT \"keyword\" FROM \"sys\".\"keywords\" ORDER BY 1");
+
+		/* An old MonetDB server (pre Jul2015 release) will not have a table sys.keywords and return an empty String */
+		return (keywords.length() > 0) ? keywords :
+			/* for old servers return static list (as returned in clients/odbc/driver/SQLGetInfo.c case SQL_KEYWORDS:) */
+			"ADMIN,AFTER,AGGREGATE,ALWAYS,ASYMMETRIC,ATOMIC," +
+			"AUTO_INCREMENT,BEFORE,BIGINT,BIGSERIAL,BINARY,BLOB," +
+			"CALL,CHAIN,CLOB,COMMITTED,COPY,CORR,CUME_DIST," +
+			"CURRENT_ROLE,CYCLE,DATABASE,DELIMITERS,DENSE_RANK," +
+			"DO,EACH,ELSEIF,ENCRYPTED,EVERY,EXCLUDE,FOLLOWING," +
+			"FUNCTION,GENERATED,IF,ILIKE,INCREMENT,LAG,LEAD," +
+			"LIMIT,LOCALTIME,LOCALTIMESTAMP,LOCKED,MAXVALUE," +
+			"MEDIAN,MEDIUMINT,MERGE,MINVALUE,NEW,NOCYCLE," +
+			"NOMAXVALUE,NOMINVALUE,NOW,OFFSET,OLD,OTHERS,OVER," +
+			"PARTITION,PERCENT_RANK,PLAN,PRECEDING,PROD,QUANTILE," +
+			"RANGE,RANK,RECORDS,REFERENCING,REMOTE,RENAME," +
+			"REPEATABLE,REPLICA,RESTART,RETURN,RETURNS," +
+			"ROW_NUMBER,ROWS,SAMPLE,SAVEPOINT,SCHEMA,SEQUENCE," +
+			"SERIAL,SERIALIZABLE,SIMPLE,START,STATEMENT,STDIN," +
+			"STDOUT,STREAM,STRING,SYMMETRIC,TIES,TINYINT,TRIGGER," +
+			"UNBOUNDED,UNCOMMITTED,UNENCRYPTED,WHILE,XMLAGG," +
+			"XMLATTRIBUTES,XMLCOMMENT,XMLCONCAT,XMLDOCUMENT," +
+			"XMLELEMENT,XMLFOREST,XMLNAMESPACES,XMLPARSE,XMLPI," +
+			"XMLQUERY,XMLSCHEMA,XMLTEXT,XMLVALIDATE";
+	}
+
+	/**
+	 * Internal utility method getConcatenatedStringFromQuery(String query)
+	 * args: query: SQL SELECT query. Only the output of the first column is concatenated.
+	 * @return a String of query result values concatenated into one string, and values separated by comma's
+	 */
+	private String getConcatenatedStringFromQuery(String query) {
+		StringBuilder sb = new StringBuilder(1024);
+		Statement st = null;
+		ResultSet rs = null;
+		try {
+			st = con.createStatement();
+			rs = st.executeQuery(query);
+			// Fetch the first column output and concatenate the values into a StringBuilder separated by comma's
+			boolean isfirst = true;
+			while (rs.next()) {
+				String value = rs.getString(1);
+				if (value != null) {
+					if (isfirst) {
+						isfirst = false;
+					} else {
+						sb.append(',');
+					}
+					sb.append(value);
+				}
+			}
+		} catch (SQLException e) {
+			/* ignore */
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+			if (st != null) {
+				try {
+					 st.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+		}
+		// for debug: System.out.println("SQL query: " + query + "\nResult string: " + sb.toString());
+		return sb.toString();
+	}
+
+	// SQL query parts shared in below four getXxxxFunctions() methods
+	private final static String FunctionsSelect = "SELECT DISTINCT \"name\" FROM \"sys\".\"functions\" ";
+	private final static String FunctionsWhere = "WHERE \"id\" IN (SELECT \"func_id\" FROM \"sys\".\"args\" WHERE \"number\" = 1 AND \"name\" = 'arg_1' AND \"type\" IN ";
+
+	@Override
+	public String getNumericFunctions() {
+		String match =
+			"('tinyint', 'smallint', 'int', 'bigint', 'hugeint', 'decimal', 'double', 'real') )" +
+			// exclude functions which belong to the 'str' module
+			" AND \"mod\" <> 'str'";
+		return getConcatenatedStringFromQuery(FunctionsSelect + FunctionsWhere + match + " ORDER BY 1");
+	}
+
+	@Override
+	public String getStringFunctions() {
+		String match =
+			"('char', 'varchar', 'clob', 'json') )" +
+			// include functions which belong to the 'str' module
+			" OR \"mod\" = 'str'";
+		return getConcatenatedStringFromQuery(FunctionsSelect + FunctionsWhere + match + " ORDER BY 1");
+	}
+
+	@Override
+	public String getSystemFunctions() {
+		String wherePart =
+			"WHERE \"id\" NOT IN (SELECT \"func_id\" FROM \"sys\".\"args\" WHERE \"number\" = 1)" +
+			" AND \"id\" IN (SELECT \"function_id\" FROM \"sys\".\"systemfunctions\")" +
+			" AND \"type\" = 1" +	// only scalar functions
+			// add system functions which are not listed in sys.functions but implemented in the SQL parser (see sql/server/sql_parser.y)
+			" UNION SELECT 'cast'" +
+			" UNION SELECT 'convert'" +
+			" UNION SELECT 'coalesce'" +
+			" UNION SELECT 'extract'" +
+			" UNION SELECT 'ifthenelse'" +
+			" UNION SELECT 'isnull'" +
+			" UNION SELECT 'nullif'";
+		return getConcatenatedStringFromQuery(FunctionsSelect + wherePart + " ORDER BY 1");
+	}
+
+	@Override
+	public String getTimeDateFunctions() {
+		String match =
+			"('date', 'time', 'timestamp', 'timetz', 'timestamptz', 'sec_interval', 'month_interval') )";
+		return getConcatenatedStringFromQuery(FunctionsSelect + FunctionsWhere + match + " ORDER BY 1");
+	}
+
+	/**
+	 * This is the string that can be used to escape '_' and '%' in
+	 * a search string pattern style catalog search parameters
+	 *
+	 * @return the string used to escape wildcard characters
+	 */
+	@Override
+	public String getSearchStringEscape() {
+		return "\\";
+	}
+
+	/**
+	 * Get all the "extra" characters that can be used in unquoted
+	 * identifier names (those beyond a-zA-Z0-9 and _)
+	 * MonetDB has no extra characters (verified it for chars: !@#$%^&*()~{}[]?
+	 *
+	 * @return a string containing the extra characters
+	 */
+	@Override
+	public String getExtraNameCharacters() {
+		return "";
+	}
+
+	/**
+	 * Is "ALTER TABLE" with an add column supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsAlterTableWithAddColumn() {
+		return true;
+	}
+
+	/**
+	 * Is "ALTER TABLE" with a drop column supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsAlterTableWithDropColumn() {
+		return true;
+	}
+
+	/**
+	 * Is column aliasing supported?
+	 *
+	 * <p>If so, the SQL AS clause can be used to provide names for
+	 * computed columns or to provide alias names for columns as
+	 * required.  A JDBC Compliant driver always returns true.
+	 *
+	 * <p>e.g.
+	 *
+	 * <br><pre>
+	 * select count(C) as C_COUNT from T group by C;
+	 *
+	 * </pre><br>
+	 * should return a column named as C_COUNT instead of count(C)
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsColumnAliasing() {
+		return true;
+	}
+
+	/**
+	 * Are concatenations between NULL and non-NULL values NULL? A
+	 * JDBC Compliant driver always returns true
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean nullPlusNonNullIsNull() {
+		return true;
+	}
+
+	/**
+	 * Retrieves whether this database supports the JDBC scalar function
+	 * CONVERT for the conversion of one JDBC type to another.
+	 * The JDBC types are the generic SQL data types defined in java.sql.Types.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsConvert() {
+		return true;
+	}
+
+	/**
+	 * Retrieves whether this database supports the JDBC scalar function
+	 * CONVERT for conversions between the JDBC types fromType and toType.
+	 * The JDBC types are the generic SQL data types defined in java.sql.Types.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsConvert(int fromType, int toType) {
+		switch (fromType) {
+		case Types.BOOLEAN:
+			switch (toType) {
+			case Types.BOOLEAN:
+		/*	case Types.BIT: is not supported by MonetDB and will fail */
+			case Types.TINYINT:
+			case Types.SMALLINT:
+			case Types.INTEGER:
+			case Types.BIGINT:
+		/* conversion to FLOAT, REAL, DOUBLE, NUMERIC and DECIMAL is not supported by MonetDB */
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+	/*	case Types.BIT: is not supported by MonetDB and will fail */
+	/*	case Types.BINARY: is not supported by MonetDB and will fail */
+	/*	case Types.VARBINARY: is not supported by MonetDB and will fail */
+	/*	case Types.LONGVARBINARY: is not supported by MonetDB and will fail */
+		case Types.BLOB:
+			switch (toType) {
+		/*	case Types.BINARY: is not supported by MonetDB and will fail */
+		/*	case Types.VARBINARY: is not supported by MonetDB and will fail */
+		/*	case Types.LONGVARBINARY: is not supported by MonetDB and will fail */
+			case Types.BLOB:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		case Types.TINYINT:
+		case Types.SMALLINT:
+		case Types.INTEGER:
+		case Types.BIGINT:
+		case Types.FLOAT:
+		case Types.REAL:
+		case Types.DOUBLE:
+		case Types.NUMERIC:
+		case Types.DECIMAL:
+			switch (toType) {
+			case Types.BOOLEAN:
+		/*	case Types.BIT: is not supported by MonetDB and will fail */
+			case Types.TINYINT:
+			case Types.SMALLINT:
+			case Types.INTEGER:
+			case Types.BIGINT:
+			case Types.FLOAT:
+			case Types.REAL:
+			case Types.DOUBLE:
+			case Types.NUMERIC:
+			case Types.DECIMAL:
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		case Types.CHAR:
+		case Types.VARCHAR:
+	/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+		case Types.CLOB:
+			switch (toType) {
+			case Types.BOOLEAN:
+		/*	case Types.BIT: is not supported by MonetDB and will fail */
+			case Types.TINYINT:
+			case Types.SMALLINT:
+			case Types.INTEGER:
+			case Types.BIGINT:
+			case Types.FLOAT:
+			case Types.REAL:
+			case Types.DOUBLE:
+			case Types.NUMERIC:
+			case Types.DECIMAL:
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+			case Types.BLOB:
+			case Types.DATE:
+			case Types.TIME:
+			case Types.TIMESTAMP:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		case Types.DATE:
+			switch (toType) {
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+			case Types.DATE:
+			case Types.TIMESTAMP:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		case Types.TIME:
+			switch (toType) {
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+			case Types.TIME:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		case Types.TIMESTAMP:
+			switch (toType) {
+			case Types.CHAR:
+			case Types.VARCHAR:
+		/*	case Types.LONGVARCHAR: is not supported by MonetDB and will fail */
+			case Types.CLOB:
+			case Types.DATE:
+			case Types.TIME:
+			case Types.TIMESTAMP:
+				return true;
+			}
+			// conversion to all other types is not supported
+			return false;
+		}
+
+		// conversion from all other JDBC SQL types are not supported
+		return false;
+	}
+
+	/**
+	 * Are table correlation names supported? A JDBC Compliant
+	 * driver always returns true.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsTableCorrelationNames() {
+		return true;
+	}
+
+	/**
+	 * If table correlation names are supported, are they restricted to
+	 * be different from the names of the tables?
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsDifferentTableCorrelationNames() {
+		return false;
+	}
+
+	/**
+	 * Are expressions in "ORDER BY" lists supported?
+	 * e.g. select * from t order by a + b;
+	 *
+	 * MonetDB supports this, try:
+	 *  select (radix * 1000) + digits as comp, * from types order by (radix * 1000) + digits, -id;
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsExpressionsInOrderBy() {
+		return true;
+	}
+
+	/**
+	 * Can an "ORDER BY" clause use columns not in the SELECT?
+	 * MonetDB differs from SQL03 =&gt; true
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsOrderByUnrelated() {
+		return true;
+	}
+
+	/**
+	 * Is some form of "GROUP BY" clause supported?
+	 *
+	 * @return true since MonetDB supports it
+	 */
+	@Override
+	public boolean supportsGroupBy() {
+		return true;
+	}
+
+	/**
+	 * Can a "GROUP BY" clause use columns not in the SELECT?
+	 *
+	 * @return true since that also is supported
+	 */
+	@Override
+	public boolean supportsGroupByUnrelated() {
+		return true;
+	}
+
+	/**
+	 * Can a "GROUP BY" clause add columns not in the SELECT provided
+	 * it specifies all the columns in the SELECT?
+	 *
+	 * (MonetDB already supports the more difficult supportsGroupByUnrelated(),
+	 * so this is a piece of cake)
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsGroupByBeyondSelect() {
+		return true;
+	}
+
+	/**
+	 * Is the escape character in "LIKE" clauses supported?  A
+	 * JDBC compliant driver always returns true.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsLikeEscapeClause() {
+		return true;
+	}
+
+	/**
+	 * Are multiple ResultSets from a single execute supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsMultipleResultSets() {
+		return true;
+	}
+
+	/**
+	 * Can we have multiple transactions open at once (on different
+	 * connections?)
+	 * This is the main idea behind the Connection, is it?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsMultipleTransactions() {
+		return true;
+	}
+
+	/**
+	 * Can columns be defined as non-nullable.	A JDBC Compliant driver
+	 * always returns true.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsNonNullableColumns() {
+		return true;
+	}
+
+	/**
+	 * Does this driver support the minimum ODBC SQL grammar.  This
+	 * grammar is defined at:
+	 *
+	 * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odappcpr.asp
+	 * From this description, we seem to support the ODBC minimal (Level 0) grammar.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsMinimumSQLGrammar() {
+		return true;
+	}
+
+	/**
+	 * Does this driver support the Core ODBC SQL grammar.	We need
+	 * SQL-92 conformance for this.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCoreSQLGrammar() {
+		return true;
+	}
+
+	/**
+	 * Does this driver support the Extended (Level 2) ODBC SQL
+	 * grammar.  We don't conform to the Core (Level 1), so we can't
+	 * conform to the Extended SQL Grammar.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsExtendedSQLGrammar() {
+		return false;
+	}
+
+	/**
+	 * Does this driver support the ANSI-92 entry level SQL grammar?
+	 * All JDBC Compliant drivers must return true. We should be this
+	 * compliant, so let's 'act' like we are.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsANSI92EntryLevelSQL() {
+		return true;
+	}
+
+	/**
+	 * Does this driver support the ANSI-92 intermediate level SQL
+	 * grammar?
+	 * probably not
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsANSI92IntermediateSQL() {
+		return false;
+	}
+
+	/**
+	 * Does this driver support the ANSI-92 full SQL grammar?
+	 * Would be good if it was like that
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsANSI92FullSQL() {
+		return false;
+	}
+
+	/**
+	 * Is the SQL Integrity Enhancement Facility supported?
+	 * Our best guess is that this means support for constraints
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsIntegrityEnhancementFacility() {
+		return true;
+	}
+
+	/**
+	 * Is some form of outer join supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsOuterJoins(){
+		return true;
+	}
+
+	/**
+	 * Are full nexted outer joins supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsFullOuterJoins() {
+		return true;
+	}
+
+	/**
+	 * Is there limited support for outer joins?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsLimitedOuterJoins() {
+		return false;
+	}
+
+	/**
+	 * What is the database vendor's preferred term for "schema"?
+	 * MonetDB uses the term "schema".
+	 *
+	 * @return the vendor term
+	 */
+	@Override
+	public String getSchemaTerm() {
+		return "schema";
+	}
+
+	/**
+	 * What is the database vendor's preferred term for "procedure"?
+	 *
+	 * @return the vendor term
+	 */
+	@Override
+	public String getProcedureTerm() {
+		return "procedure";
+	}
+
+	/**
+	 * What is the database vendor's preferred term for "catalog"?
+	 * MonetDB doesn't really have them (from driver accessible) but
+	 * from the monetdb.conf file the term "database" sounds best
+	 *
+	 * @return the vendor term
+	 */
+	@Override
+	public String getCatalogTerm() {
+		return "database";
+	}
+
+	/**
+	 * Does a catalog appear at the start of a qualified table name?
+	 * (Otherwise it appears at the end).
+	 * Currently there is no catalog support at all in MonetDB
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean isCatalogAtStart() {
+		// return true here; we return false for every other catalog function
+		// so it won't matter what we return here
+		return true;
+	}
+
+	/**
+	 * What is the Catalog separator.
+	 *
+	 * @return the catalog separator string
+	 */
+	@Override
+	public String getCatalogSeparator() {
+		// MonetDB does NOT support catalogs, so also no catalog separator
+		return null;
+	}
+
+	/**
+	 * Can a schema name be used in a data manipulation statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsSchemasInDataManipulation() {
+		return true;
+	}
+
+	/**
+	 * Can a schema name be used in a procedure call statement?
+	 * Ohw probably, but I don't know of procedures in MonetDB
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsSchemasInProcedureCalls() {
+		return true;
+	}
+
+	/**
+	 * Can a schema be used in a table definition statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsSchemasInTableDefinitions() {
+		return true;
+	}
+
+	/**
+	 * Can a schema name be used in an index definition statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsSchemasInIndexDefinitions() {
+		return true;
+	}
+
+	/**
+	 * Can a schema name be used in a privilege definition statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsSchemasInPrivilegeDefinitions() {
+		return true;
+	}
+
+	/**
+	 * Can a catalog name be used in a data manipulation statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCatalogsInDataManipulation() {
+		return false;
+	}
+
+	/**
+	 * Can a catalog name be used in a procedure call statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCatalogsInProcedureCalls() {
+		return false;
+	}
+
+	/**
+	 * Can a catalog name be used in a table definition statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCatalogsInTableDefinitions() {
+		return false;
+	}
+
+	/**
+	 * Can a catalog name be used in an index definition?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCatalogsInIndexDefinitions() {
+		return false;
+	}
+
+	/**
+	 * Can a catalog name be used in a privilege definition statement?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsCatalogsInPrivilegeDefinitions() {
+		return false;
+	}
+
+	/**
+	 * MonetDB doesn't support positioned DELETEs I guess
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsPositionedDelete() {
+		return false;
+	}
+
+	/**
+	 * Is positioned UPDATE supported? (same as above)
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsPositionedUpdate() {
+		return false;
+	}
+
+	/**
+	 * Is SELECT FOR UPDATE supported?
+	 * My test resulted in a negative answer
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsSelectForUpdate(){
+		return false;
+	}
+
+	/**
+	 * Are stored procedure calls using the stored procedure escape
+	 * syntax supported?
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsStoredProcedures() {
+		return true;
+	}
+
+	/**
+	 * Are subqueries in comparison expressions supported? A JDBC
+	 * Compliant driver always returns true. MonetDB also supports this
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsSubqueriesInComparisons() {
+		return true;
+	}
+
+	/**
+	 * Are subqueries in 'exists' expressions supported? A JDBC
+	 * Compliant driver always returns true.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsSubqueriesInExists() {
+		return true;
+	}
+
+	/**
+	 * Are subqueries in 'in' statements supported? A JDBC
+	 * Compliant driver always returns true.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsSubqueriesInIns() {
+		return true;
+	}
+
+	/**
+	 * Are subqueries in quantified expressions supported? A JDBC
+	 * Compliant driver always returns true.
+	 *
+	 * (No idea what this is, but we support a good deal of
+	 * subquerying.)
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsSubqueriesInQuantifieds() {
+		return true;
+	}
+
+	/**
+	 * Are correlated subqueries supported? A JDBC Compliant driver
+	 * always returns true.
+	 *
+	 * (a.k.a. subselect in from?)
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsCorrelatedSubqueries() {
+		return true;
+	}
+
+	/**
+	 * Is SQL UNION supported?
+	 * since 2004-03-20
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsUnion() {
+		return true;
+	}
+
+	/**
+	 * Is SQL UNION ALL supported?
+	 * since 2004-03-20
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsUnionAll() {
+		return true;
+	}
+
+	/**
+	 * ResultSet objects (cursors) are not closed upon explicit or
+	 * implicit commit.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsOpenCursorsAcrossCommit() {
+		return true;
+	}
+
+	/**
+	 * Same as above
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsOpenCursorsAcrossRollback() {
+		return true;
+	}
+
+	/**
+	 * Can statements remain open across commits?  They may, but
+	 * this driver cannot guarentee that.  In further reflection.
+	 * we are taking a Statement object here, so the answer is
+	 * yes, since the Statement is only a vehicle to execute some SQL
+	 *
+	 * @return true if they always remain open; false otherwise
+	 */
+	@Override
+	public boolean supportsOpenStatementsAcrossCommit() {
+		return true;
+	}
+
+	/**
+	 * Can statements remain open across rollbacks?  They may, but
+	 * this driver cannot guarentee that.  In further contemplation,
+	 * we are taking a Statement object here, so the answer is yes again.
+	 *
+	 * @return true if they always remain open; false otherwise
+	 */
+	@Override
+	public boolean supportsOpenStatementsAcrossRollback() {
+		return true;
+	}
+
+	/**
+	 * How many hex characters can you have in an inline binary literal
+	 * I honestly wouldn't know...
+	 *
+	 * @return the max literal length
+	 */
+	@Override
+	public int getMaxBinaryLiteralLength() {
+		return 0; // no limit
+	}
+
+	/**
+	 * What is the maximum length for a character literal
+	 * Is there a max?
+	 *
+	 * @return the max literal length
+	 */
+	@Override
+	public int getMaxCharLiteralLength() {
+		return 0; // no limit
+	}
+
+	/**
+	 * Whats the limit on column name length.
+	 * I take some safety here, but it's just a varchar in MonetDB
+	 *
+	 * @return the maximum column name length
+	 */
+	@Override
+	public int getMaxColumnNameLength() {
+		return 1024;
+	}
+
+	/**
+	 * What is the maximum number of columns in a "GROUP BY" clause?
+	 *
+	 * @return the max number of columns
+	 */
+	@Override
+	public int getMaxColumnsInGroupBy() {
+		return 0; // no limit
+	}
+
+	/**
+	 * What's the maximum number of columns allowed in an index?
+	 *
+	 * @return max number of columns
+	 */
+	@Override
+	public int getMaxColumnsInIndex() {
+		return 0;	// unlimited I guess
+	}
+
+	/**
+	 * What's the maximum number of columns in an "ORDER BY clause?
+	 *
+	 * @return the max columns
+	 */
+	@Override
+	public int getMaxColumnsInOrderBy() {
+		return 0; // unlimited I guess
+	}
+
+	/**
+	 * What is the maximum number of columns in a "SELECT" list?
+	 *
+	 * @return the max columns
+	 */
+	@Override
+	public int getMaxColumnsInSelect() {
+		return 0; // unlimited I guess
+	}
+
+	/**
+	 * What is the maximum number of columns in a table?
+	 * wasn't MonetDB designed for datamining? (= much columns)
+	 *
+	 * @return the max columns
+	 */
+	@Override
+	public int getMaxColumnsInTable() {
+		return 0;
+	}
+
+	/**
+	 * How many active connections can we have at a time to this
+	 * database?  Well, since it depends on Mserver, which just listens
+	 * for new connections and creates a new thread for each connection,
+	 * this number can be very high, and theoretically till the system
+	 * runs out of resources. However, knowing MonetDB is knowing that you
+	 * should handle it a little bit with care, so I give a very minimalistic
+	 * number here.
+	 *
+	 * @return the maximum number of connections
+	 */
+	@Override
+	public int getMaxConnections() {
+		if (env_max_clients == null)
+			getEnvValues();
+
+		int max_clients = 16;
+		if (env_max_clients != null) {
+			try {
+				max_clients = Integer.parseInt(env_max_clients);
+			} catch (NumberFormatException nfe) { /* ignore */ }
+		}
+		return max_clients;
+	}
+
+	/**
+	 * What is the maximum cursor name length
+	 * Actually we do not do named cursors, so I keep the value small as
+	 * a precaution for maybe the future.
+	 *
+	 * @return max cursor name length in bytes
+	 */
+	@Override
+	public int getMaxCursorNameLength() {
+		return 1024;
+	}
+
+	/**
+	 * Retrieves the maximum number of bytes for an index, including all
+	 * of the parts of the index.
+	 *
+	 * @return max index length in bytes, which includes the composite
+	 *         of all the constituent parts of the index; a result of zero
+	 *         means that there is no limit or the limit is not known
+	 */
+	@Override
+	public int getMaxIndexLength() {
+		return 0; // I assume it is large, but I don't know
+	}
+
+	/**
+	 * Retrieves the maximum number of characters that this database
+	 * allows in a schema name.
+	 *
+	 * @return the number of characters or 0 if there is no limit, or the
+	 *         limit is unknown.
+	 */
+	@Override
+	public int getMaxSchemaNameLength() {
+		return 1024;
+	}
+
+	/**
+	 * What is the maximum length of a procedure name
+	 *
+	 * @return the max name length in bytes
+	 */
+	@Override
+	public int getMaxProcedureNameLength() {
+		return 1024;
+	}
+
+	/**
+	 * What is the maximum length of a catalog
+	 *
+	 * @return the maximum number of characters allowed in a catalog name;
+	 * a result of zero means that there is no limit or the limit is not known
+	 */
+	@Override
+	public int getMaxCatalogNameLength() {
+		return 0;  // MonetDB does not support catalog names
+	}
+
+	/**
+	 * What is the maximum length of a single row?
+	 *
+	 * @return max row size in bytes
+	 */
+	@Override
+	public int getMaxRowSize() {
+		return 0;	// very long I hope...
+	}
+
+	/**
+	 * Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY
+	 * blobs?
+	 * Yes I thought so...
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean doesMaxRowSizeIncludeBlobs() {
+		return true;
+	}
+
+	/**
+	 * What is the maximum length of a SQL statement?
+	 * Till a programmer makes a mistake and causes a segmentation fault
+	 * on a string overflow...
+	 *
+	 * @return max length in bytes
+	 */
+	@Override
+	public int getMaxStatementLength() {
+		return 0;		// actually whatever fits in size_t
+	}
+
+	/**
+	 * How many active statements can we have open at one time to
+	 * this database?  Basically, since each Statement downloads
+	 * the results as the query is executed, we can have many.
+	 *
+	 * @return the maximum
+	 */
+	@Override
+	public int getMaxStatements() {
+		return 0;
+	}
+
+	/**
+	 * What is the maximum length of a table name
+	 *
+	 * @return max name length in bytes
+	 */
+	@Override
+	public int getMaxTableNameLength() {
+		return 1024;
+	}
+
+	/**
+	 * What is the maximum number of tables that can be specified
+	 * in a SELECT?
+	 *
+	 * @return the maximum
+	 */
+	@Override
+	public int getMaxTablesInSelect() {
+		return 0; // no limit
+	}
+
+	/**
+	 * What is the maximum length of a user name
+	 *
+	 * @return the max name length in bytes
+	 */
+	@Override
+	public int getMaxUserNameLength() {
+		return 512;
+	}
+
+	/**
+	 * What is the database's default transaction isolation level?
+	 * We only see commited data, nonrepeatable reads and phantom
+	 * reads can occur.
+	 *
+	 * @return the default isolation level
+	 * @see Connection
+	 */
+	@Override
+	public int getDefaultTransactionIsolation() {
+		return Connection.TRANSACTION_SERIALIZABLE;
+	}
+
+	/**
+	 * Are transactions supported?	If not, commit and rollback are noops
+	 * and the isolation level is TRANSACTION_NONE.  We do support
+	 * transactions.
+	 *
+	 * @return true if transactions are supported
+	 */
+	@Override
+	public boolean supportsTransactions() {
+		return true;
+	}
+
+	/**
+	 * Does the database support the given transaction isolation level?
+	 * We only support TRANSACTION_READ_COMMITTED as far as I know
+	 *
+	 * @param level the values are defined in java.sql.Connection
+	 * @return true if so
+	 * @see Connection
+	 */
+	@Override
+	public boolean supportsTransactionIsolationLevel(int level) {
+		return level == Connection.TRANSACTION_SERIALIZABLE;
+	}
+
+	/**
+	 * Are both data definition and data manipulation transactions
+	 * supported?
+	 * Supposedly that data definition is like CREATE or ALTER TABLE
+	 * yes it is.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsDataDefinitionAndDataManipulationTransactions() {
+		return true;
+	}
+
+	/**
+	 * Are only data manipulation statements within a transaction
+	 * supported?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean supportsDataManipulationTransactionsOnly() {
+		return false;
+	}
+
+	/**
+	 * Does a data definition statement within a transaction force
+	 * the transaction to commit?  I think this means something like:
+	 *
+	 * <p><pre>
+	 * CREATE TABLE T (A INT);
+	 * INSERT INTO T (A) VALUES (2);
+	 * BEGIN;
+	 * UPDATE T SET A = A + 1;
+	 * CREATE TABLE X (A INT);
+	 * SELECT A FROM T INTO X;
+	 * COMMIT;
+	 * </pre></p>
+	 *
+	 * does the CREATE TABLE call cause a commit?  The answer is no.
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean dataDefinitionCausesTransactionCommit() {
+		return false;
+	}
+
+	/**
+	 * Is a data definition statement within a transaction ignored?
+	 *
+	 * @return true if so
+	 */
+	@Override
+	public boolean dataDefinitionIgnoredInTransactions() {
+		return false;
+	}
+
+	/**
+	 * Get a description of stored procedures available in a catalog
+	 *
+	 * <p>Only procedure descriptions matching the schema and procedure
+	 * name criteria are returned.	They are ordered by PROCEDURE_SCHEM,
+	 *  PROCEDURE_NAME and SPECIFIC_NAME.
+	 *
+	 * <p>Each procedure description has the following columns:
+	 * <ol>
+	 * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null)
+	 * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null)
+	 * <li><b>PROCEDURE_NAME</b> String => procedure name
+	 * <li><b>Field4</b> reserved (make it null)
+	 * <li><b>Field5</b> reserved (make it null)
+	 * <li><b>Field6</b> reserved (make it null)
+	 * <li><b>REMARKS</b> String => explanatory comment on the procedure
+	 * <li><b>PROCEDURE_TYPE</b> short => kind of procedure
+	 *	<ul>
+	 *	  <li> procedureResultUnknown - May return a result
+	 *	  <li> procedureNoResult - Does not return a result
+	 *	  <li> procedureReturnsResult - Returns a result
+	 *	</ul>
+	 * </ol>
+	 * <li><b>SPECIFIC_NAME</b> String => The name which uniquely identifies this procedure within its schema.
+	 *
+	 * @param catalog - a catalog name; must match the catalog name as it is stored in the database;
+	 *	"" retrieves those without a catalog;
+	 *	null means that the catalog name should not be used to narrow the search
+	 * @param schemaPattern - a schema name pattern; must match the schema name as it is stored in the database;
+	 *	"" retrieves those without a schema;
+	 *	null means that the schema name should not be used to narrow the search
+	 * @param procedureNamePattern - a procedure name pattern; must match the procedure name as it is stored in the database
+	 * @return ResultSet - each row is a procedure description
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getProcedures(
+		String catalog,
+		String schemaPattern,
+		String procedureNamePattern
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(980);
+		query.append("SELECT cast(null as varchar(1)) AS \"PROCEDURE_CAT\", " +
+			"\"schemas\".\"name\" AS \"PROCEDURE_SCHEM\", " +
+			"\"functions\".\"name\" AS \"PROCEDURE_NAME\", " +
+			"cast(null as char(1)) AS \"Field4\", " +
+			"cast(null as char(1)) AS \"Field5\", " +
+			"cast(null as char(1)) AS \"Field6\", " +
+			"cast(\"functions\".\"func\" as varchar(9999)) AS \"REMARKS\", " +
+			// in MonetDB procedures have no return value by design.
+			"CAST(").append(DatabaseMetaData.procedureNoResult).append(" AS smallint) AS \"PROCEDURE_TYPE\", " +
+			// only the id value uniquely identifies a procedure. Include it to be able to differentiate between multiple overloaded procedures with the same name
+			"cast(\"functions\".\"id\" as varchar(10)) AS \"SPECIFIC_NAME\" " +
+		"FROM \"sys\".\"functions\" JOIN \"sys\".\"schemas\" ON (\"functions\".\"schema_id\" = \"schemas\".\"id\") " +
+		// include procedures only (type = 2). Others will be returned via getFunctions()
+		"WHERE \"functions\".\"type\" = 2");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (procedureNamePattern != null) {
+			query.append(" AND \"functions\".\"name\" ").append(composeMatchPart(procedureNamePattern));
+		}
+
+		query.append(" ORDER BY \"PROCEDURE_SCHEM\", \"PROCEDURE_NAME\", \"SPECIFIC_NAME\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of a catalog's stored procedure parameters
+	 * and result columns.
+	 *
+	 * <p>Only descriptions matching the schema, procedure and parameter name
+	 * criteria are returned. They are ordered by PROCEDURE_SCHEM, PROCEDURE_NAME
+	 * and SPECIFIC_NAME. Within this, the return value, if any, is first.
+	 * Next are the parameter descriptions in call order. The
+	 * column descriptions follow in column number order.
+	 *
+	 * <p>Each row in the ResultSet is a parameter description or column
+	 * description with the following fields:
+	 * <ol>
+	 * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null)
+	 * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null)
+	 * <li><b>PROCEDURE_NAME</b> String => procedure name
+	 * <li><b>COLUMN_NAME</b> String => column/parameter name
+	 * <li><b>COLUMN_TYPE</b> Short => kind of column/parameter:
+	 * <ul><li>procedureColumnUnknown - nobody knows
+	 * <li>procedureColumnIn - IN parameter
+	 * <li>procedureColumnInOut - INOUT parameter
+	 * <li>procedureColumnOut - OUT parameter
+	 * <li>procedureColumnReturn - procedure return value
+	 * <li>procedureColumnResult - result column in ResultSet
+	 * </ul>
+	 * <li><b>DATA_TYPE</b> int => SQL type from java.sql.Types
+	 * <li><b>TYPE_NAME</b> String => SQL type name, for a UDT type the type name is fully qualified
+	 * <li><b>PRECISION</b> int => precision
+	 * <li><b>LENGTH</b> int => length in bytes of data
+	 * <li><b>SCALE</b> short => scale - null is returned for data types where SCALE is not applicable.
+	 * <li><b>RADIX</b> short => radix
+	 * <li><b>NULLABLE</b> short => can it contain NULL?
+	 * <ul><li>procedureNoNulls - does not allow NULL values
+	 * <li>procedureNullable - allows NULL values
+	 * <li>procedureNullableUnknown - nullability unknown
+	 * </ul>
+	 * <li><b>REMARKS</b> String => comment describing parameter/column
+	 * <li><b>COLUMN_DEF</b> String => default value for the column, which should be interpreted as a string when the value is enclosed in single quotes (may be null)
+	 *         The string NULL (not enclosed in quotes) - if NULL was specified as the default value
+	 *         TRUNCATE (not enclosed in quotes) - if the specified default value cannot be represented without truncation
+	 *         NULL - if a default value was not specified
+	 * <li><b>SQL_DATA_TYPE</b> int => reserved for future use
+	 * <li><b>SQL_DATETIME_SUB</b> int => reserved for future use
+	 * <li><b>CHAR_OCTET_LENGTH</b> int => the maximum length of binary and character based columns. For any other datatype the returned value is a NULL
+	 * <li><b>ORDINAL_POSITION</b> int => the ordinal position, starting from 1, for the input and output parameters for a procedure.
+	 *	A value of 0 is returned if this row describes the procedure's return value. For result set columns, it is the ordinal position of the
+	 *	column in the result set starting from 1. If there are multiple result sets, the column ordinal positions are implementation defined.
+	 * <li><b>IS_NULLABLE</b> String => ISO rules are used to determine the nullability for a column.
+	 * <ul><li>YES --- if the parameter can include NULLs
+	 * <li>NO --- if the parameter cannot include NULLs
+	 * <li>empty string --- if the nullability for the parameter is unknown
+	 * </ul>
+	 * <li><b>SPECIFIC_NAME</b> String => the name which uniquely identifies this procedure within its schema.
+	 * </ol>
+	 * @param catalog - a catalog name; must match the catalog name as it is stored in the database;
+	 *	"" retrieves those without a catalog;
+	 *	null means that the catalog name should not be used to narrow the search
+	 * @param schemaPattern - a schema name pattern; must match the schema name as it is stored in the database;
+	 *	"" retrieves those without a schema;
+	 *	null means that the schema name should not be used to narrow the search
+	 * @param procedureNamePattern - a procedure name pattern; must match the procedure name as it is stored in the database
+	 * @param columnNamePattern - a column name pattern; must match the column name as it is stored in the database
+	 * @return ResultSet - each row describes a stored procedure parameter or column
+	 * @throws SQLException if a database-access error occurs
+	 * @see #getSearchStringEscape
+	 */
+	@Override
+	public ResultSet getProcedureColumns(
+		String catalog,
+		String schemaPattern,
+		String procedureNamePattern,
+		String columnNamePattern
+	) throws SQLException {
+		StringBuilder query = new StringBuilder(2900);
+		query.append("SELECT cast(null as varchar(1)) AS \"PROCEDURE_CAT\", " +
+			"\"schemas\".\"name\" AS \"PROCEDURE_SCHEM\", " +
+			"\"functions\".\"name\" AS \"PROCEDURE_NAME\", " +
+			"\"args\".\"name\" AS \"COLUMN_NAME\", " +
+			"CAST(CASE \"args\".\"inout\"" +
+				" WHEN 0 THEN (CASE \"args\".\"number\" WHEN 0 THEN ").append(DatabaseMetaData.procedureColumnReturn).append(" ELSE ").append(DatabaseMetaData.procedureColumnOut).append(" END)" +
+				" WHEN 1 THEN ").append(DatabaseMetaData.procedureColumnIn)
+				.append(" ELSE ").append(DatabaseMetaData.procedureColumnUnknown).append(" END AS smallint) AS \"COLUMN_TYPE\", " +
+			"CAST(").append(MonetDriver.getSQLTypeMap("\"args\".\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
+			"\"args\".\"type\" AS \"TYPE_NAME\", " +
+			"CASE \"args\".\"type\" WHEN 'tinyint' THEN 3 WHEN 'smallint' THEN 5 WHEN 'int' THEN 10 WHEN 'bigint' THEN 19 WHEN 'hugeint' THEN 38 WHEN 'oid' THEN 19 WHEN 'wrd' THEN 19 ELSE \"args\".\"type_digits\" END AS \"PRECISION\", " +
+			"CASE \"args\".\"type\" WHEN 'tinyint' THEN 1 WHEN 'smallint' THEN 2 WHEN 'int' THEN 4 WHEN 'bigint' THEN 8 WHEN 'hugeint' THEN 16 WHEN 'oid' THEN 8 WHEN 'wrd' THEN 8 ELSE \"args\".\"type_digits\" END AS \"LENGTH\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('tinyint','smallint','int','bigint','hugeint','oid','wrd','decimal','numeric','time','timetz','timestamp','timestamptz','sec_interval') THEN \"args\".\"type_scale\" ELSE NULL END AS smallint) AS \"SCALE\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('tinyint','smallint','int','bigint','hugeint','oid','wrd','decimal','numeric') THEN 10 WHEN \"args\".\"type\" IN ('real','float','double') THEN 2 ELSE NULL END AS smallint) AS \"RADIX\", " +
+			"CAST(").append(DatabaseMetaData.procedureNullableUnknown).append(" AS smallint) AS \"NULLABLE\", " +
+			"CAST(null as char(1)) AS \"REMARKS\", " +
+			"CAST(null as char(1)) AS \"COLUMN_DEF\", " +
+			"CAST(0 as int) AS \"SQL_DATA_TYPE\", " +
+			"CAST(0 as int) AS \"SQL_DATETIME_SUB\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('char','varchar','clob') THEN \"args\".\"type_digits\" ELSE NULL END as int) AS \"CHAR_OCTET_LENGTH\", " +
+			// in MonetDB procedures have no return value by design. The arguments in sys.args are numbered from 0 so we must add 1 to comply with the API specification.
+			"CAST(\"args\".\"number\" + 1 as int) AS \"ORDINAL_POSITION\", " +
+			"CAST('' as varchar(3)) AS \"IS_NULLABLE\", " +
+			// the specific name contains the function id, in order to be able to match the args to the correct overloaded procedure name
+			"cast(\"functions\".\"id\" as varchar(10)) AS \"SPECIFIC_NAME\" " +
+		"FROM \"sys\".\"args\", \"sys\".\"functions\", \"sys\".\"schemas\" " +
+		"WHERE \"args\".\"func_id\" = \"functions\".\"id\" " +
+		"AND \"functions\".\"schema_id\" = \"schemas\".\"id\" " +
+		// include procedures only (type = 2). Others will be returned via getFunctionColumns()
+		"AND \"functions\".\"type\" = 2");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (procedureNamePattern != null) {
+			query.append(" AND \"functions\".\"name\" ").append(composeMatchPart(procedureNamePattern));
+		}
+		if (columnNamePattern != null) {
+			query.append(" AND \"args\".\"name\" ").append(composeMatchPart(columnNamePattern));
+		}
+		query.append(" ORDER BY \"PROCEDURE_SCHEM\", \"PROCEDURE_NAME\", \"SPECIFIC_NAME\", \"ORDINAL_POSITION\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+
+	//== this is a helper method which does not belong to the interface
+
+	/**
+	 * Returns a SQL match part string where depending on the input value we
+	 * compose an exact match (use =) or match with wildcards (use LIKE)
+	 *
+	 * @param in the string to match
+	 * @return the SQL match part string
+	 */
+	private static final String composeMatchPart(String in) {
+		if (in == null)
+			return "IS NULL";
+
+		String sql = "= '";
+		// check if SQL wildcards are used in the input, if so use LIKE
+		if (in.contains("%") || in.contains("_"))
+			sql = "LIKE '";
+
+		// all slashes and single quotes in input are escaped with a slash.
+		String escaped = in.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'");
+
+		return sql + escaped + "'";
+	}
+
+	/**
+	 * Returns the given string between two double quotes for usage as
+	 * exact column or table name in SQL queries.
+	 *
+	 * @param in the string to quote
+	 * @return the quoted string
+	 */
+//	@SuppressWarnings("unused")
+//	private static final String dq(String in) {
+//		return "\"" + in.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\"";
+//	}
+
+	//== end helper methods
+
+
+	/**
+	 * Retrieves a description of the tables available in the given catalog.
+	 * Only table descriptions matching the catalog, schema, table name and type criteria are returned.
+	 * They are ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM and TABLE_NAME.
+	 *
+	 * <p>Each table description has the following columns:
+	 *
+	 * <ol>
+	 * <li><b>TABLE_CAT</b> String => table catalog (may be null)
+	 * <li><b>TABLE_SCHEM</b> String => table schema (may be null)
+	 * <li><b>TABLE_NAME</b> String => table name
+	 * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE",
+	 * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
+	 * <li><b>REMARKS</b> String => explanatory comment on the table
+	 * <li><b>TYPE_CAT</b> String => the types catalog (may be null)
+	 * <li><b>TYPE_SCHEM</b> String => the types schema (may be null)
+	 * <li><b>TYPE_NAME</b> String => type name (may be null)
+	 * <li><b>SELF_REFERENCING_COL_NAME</b> String => name of the designated "identifier" column of a typed table (may be null)
+	 * <li><b>REF_GENERATION</b> String => specifies how values in SELF_REFERENCING_COL_NAME are created. Values are "SYSTEM", "USER", "DERIVED". (may be null)
+	 * </ol>
+	 *
+	 * @param catalog - a catalog name; must match the catalog name as it is stored in the database;
+	 *	"" retrieves those without a catalog; null means that the
+	 *	catalog name should not be used to narrow the search
+	 * @param schemaPattern - a schema name pattern; must match the schema name as it is stored
+	 *	in the database; "" retrieves those without a schema;
+	 *	null means that the schema name should not be used to narrow the search
+	 * @param tableNamePattern - a table name pattern; must match the table name as it is stored in the database
+	 *	For all tables this should be "%"
+	 * @param types - a list of table types, which must be from the list of table types returned
+	 *	from getTableTypes(),to include; null returns all types
+	 * @return ResultSet - each row is a table description
+	 * @throws SQLException if a database-access error occurs.
+	 */
+	@Override
+	public ResultSet getTables(
+		String catalog,
+		String schemaPattern,
+		String tableNamePattern,
+		String types[]
+	) throws SQLException
+	{
+		// as of Jul2015 release the sys.tables.type values (0 through 6) is extended with new values 10, 11, 20, and 30 (for system and temp tables/views).
+		// as of Jul2015 release we also have a new table: sys.table_types with names for the new table types
+		// for correct behavior we need to know if the server is using the old (pre Jul2015) or new sys.tables.type values
+		boolean preJul2015 = ("11.19.15".compareTo(getDatabaseProductVersion()) >= 0);
+		/* for debug: System.out.println("getDatabaseProductVersion() is " + getDatabaseProductVersion() + "  preJul2015 is " + preJul2015); */
+
+		StringBuilder query = new StringBuilder(1600);
+		if (preJul2015 && types != null && types.length > 0) {
+			// we need to filter on the constructed "TABLE_TYPE" expression, this is only possible when we use a subquery in the FROM
+			query.append("SELECT * FROM (");
+		}
+		query.append("SELECT DISTINCT cast(null as char(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", ");
+		if (preJul2015) {
+			query.append(
+				"CASE WHEN \"tables\".\"system\" = true AND \"tables\".\"type\" IN (0, 10) AND \"tables\".\"temporary\" = 0 THEN 'SYSTEM TABLE' " +
+				"WHEN \"tables\".\"system\" = true AND \"tables\".\"type\" IN (1, 11) AND \"tables\".\"temporary\" = 0 THEN 'SYSTEM VIEW' " +
+				"WHEN \"tables\".\"system\" = false AND \"tables\".\"type\" = 0 AND \"tables\".\"temporary\" = 0 THEN 'TABLE' " +
+				"WHEN \"tables\".\"system\" = false AND \"tables\".\"type\" = 1 AND \"tables\".\"temporary\" = 0 THEN 'VIEW' " +
+				"WHEN \"tables\".\"system\" = true AND \"tables\".\"type\" IN (0, 20) AND \"tables\".\"temporary\" = 1 THEN 'SYSTEM SESSION TABLE' " +
+				"WHEN \"tables\".\"system\" = true AND \"tables\".\"type\" IN (1, 21) AND \"tables\".\"temporary\" = 1 THEN 'SYSTEM SESSION VIEW' " +
+				"WHEN \"tables\".\"system\" = false AND \"tables\".\"type\" IN (0, 30) AND \"tables\".\"temporary\" = 1 THEN 'SESSION TABLE' " +
+				"WHEN \"tables\".\"system\" = false AND \"tables\".\"type\" IN (1, 31) AND \"tables\".\"temporary\" = 1 THEN 'SESSION VIEW' " +
+				"END AS \"TABLE_TYPE\", ");
+		} else {
+			query.append("\"table_types\".\"table_type_name\" AS \"TABLE_TYPE\", ");
+		}
+		query.append("\"tables\".\"query\" AS \"REMARKS\", " +
+			"cast(null as char(1)) AS \"TYPE_CAT\", " +
+			"cast(null as char(1)) AS \"TYPE_SCHEM\", " +
+			"cast(null as char(1)) AS \"TYPE_NAME\", " +
+			"cast(null as char(1)) AS \"SELF_REFERENCING_COL_NAME\", " +
+			"cast(null as char(1)) AS \"REF_GENERATION\" " +
+			"FROM \"sys\".\"tables\", \"sys\".\"schemas\"");
+		if (!preJul2015) {
+			query.append(", \"sys\".\"table_types\"");
+		}
+		query.append(" WHERE \"tables\".\"schema_id\" = \"schemas\".\"id\"");
+		if (!preJul2015) {
+			query.append(" AND \"tables\".\"type\" = \"table_types\".\"table_type_id\"");
+		}
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (tableNamePattern != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(tableNamePattern));
+		}
+		if (types != null && types.length > 0) {
+			if (preJul2015) {
+				query.append(") AS \"getTables\" WHERE \"TABLE_TYPE\" IN (");
+			} else {
+				query.append(" AND \"table_types\".\"table_type_name\" IN (");
+			}
+			for (int i = 0; i < types.length; i++) {
+				if (i > 0) {
+					query.append(", ");
+				}
+				query.append("'").append(types[i]).append("'");
+			}
+			query.append(")");
+		}
+
+		query.append(" ORDER BY \"TABLE_TYPE\", \"TABLE_SCHEM\", \"TABLE_NAME\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get the schema names available in this database.  The results
+	 * are ordered by schema name.
+	 *
+	 * <P>The schema column is:
+	 *	<OL>
+	 *	<LI><B>TABLE_SCHEM</B> String => schema name
+	 *	<LI><B>TABLE_CATALOG</B> String => catalog name (may be null)
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; must match the catalog name as it
+	 *        is stored in the database;"" retrieves those without a
+	 *        catalog; null means catalog name should not be used to
+	 *        narrow down the search.
+	 * @param schemaPattern a schema name; must match the schema name as
+	 *        it is stored in the database; null means schema name
+	 *        should not be used to narrow down the search.
+	 * @return ResultSet each row has a single String column that is a
+	 *         schema name
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getSchemas(String catalog, String schemaPattern)
+		throws SQLException
+	{
+		StringBuilder query = new StringBuilder(170);
+		query.append("SELECT \"name\" AS \"TABLE_SCHEM\", " +
+				"cast(null as char(1)) AS \"TABLE_CATALOG\" " +
+			"FROM \"sys\".\"schemas\"");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" WHERE 1 = 0");
+		} else {
+			if (schemaPattern != null) {
+				query.append(" WHERE \"name\" ").append(composeMatchPart(schemaPattern));
+			}
+		}
+		query.append(" ORDER BY \"TABLE_SCHEM\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get the catalog names available in this database.  The results
+	 * are ordered by catalog name.
+	 *
+	 * <P>The catalog column is:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => catalog name
+	 *	</OL>
+	 *
+	 *
+	 * @return ResultSet each row has a single String column that is a
+	 *         catalog name
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getCatalogs() throws SQLException {
+		// MonetDB does NOT support catalogs.
+		// Return a resultset with no rows
+		return executeMetaDataQuery("SELECT cast(null as char(1)) AS \"TABLE_CAT\" WHERE 1 = 0");
+	}
+
+	/**
+	 * Get the table types available in this database. The results
+	 * are ordered by table type.
+	 *
+	 * <P>The table type is:
+	 *	<OL>
+	 *	<LI><B>TABLE_TYPE</B> String => table type.  Typical types are "TABLE",
+	 *			"VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
+	 *			"LOCAL TEMPORARY", "ALIAS", "SYNONYM".
+	 *	</OL>
+	 *
+	 * @return ResultSet each row has a single String column that is a
+	 *         table type
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getTableTypes() throws SQLException {
+		// as of Jul2015 release we have a new table: sys.table_types with more table types
+		String query = "SELECT \"table_type_name\" AS \"TABLE_TYPE\" FROM \"sys\".\"table_types\" ORDER BY 1";
+		// For old (pre jul2015) servers fall back to old behavior.
+		boolean preJul2015 = ("11.19.15".compareTo(getDatabaseProductVersion()) >= 0);
+		if (preJul2015) {
+			query = "SELECT 'SESSION TABLE' AS \"TABLE_TYPE\" UNION ALL " +
+				"SELECT 'SESSION VIEW' UNION ALL " +
+				"SELECT 'SYSTEM SESSION TABLE' UNION ALL " +
+				"SELECT 'SYSTEM SESSION VIEW' UNION ALL " +
+				"SELECT 'SYSTEM TABLE' UNION ALL " +
+				"SELECT 'SYSTEM VIEW' UNION ALL " +
+				"SELECT 'TABLE' UNION ALL " +
+				"SELECT 'VIEW' ORDER BY 1";
+		}
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Get a description of table columns available in a catalog.
+	 *
+	 * <P>Only column descriptions matching the catalog, schema, table
+	 * and column name criteria are returned.  They are ordered by
+	 * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION.
+	 *
+	 * <P>Each column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => table catalog (may be null)
+	 *	<LI><B>TABLE_SCHEM</B> String => table schema (may be null)
+	 *	<LI><B>TABLE_NAME</B> String => table name
+	 *	<LI><B>COLUMN_NAME</B> String => column name
+	 *	<LI><B>DATA_TYPE</B> int => SQL type from java.sql.Types
+	 *	<LI><B>TYPE_NAME</B> String => Data source dependent type name
+	 *	<LI><B>COLUMN_SIZE</B> int => column size.	For char or date
+	 *		types this is the maximum number of characters, for numeric or
+	 *		decimal types this is precision.
+	 *	<LI><B>BUFFER_LENGTH</B> is not used.
+	 *	<LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits
+	 *	<LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2)
+	 *	<LI><B>NULLABLE</B> int => is NULL allowed?
+	 *		<UL>
+	 *		<LI> columnNoNulls - might not allow NULL values
+	 *		<LI> columnNullable - definitely allows NULL values
+	 *		<LI> columnNullableUnknown - nullability unknown
+	 *		</UL>
+	 *	<LI><B>REMARKS</B> String => comment describing column (may be null)
+	 *	<LI><B>COLUMN_DEF</B> String => default value (may be null)
+	 *	<LI><B>SQL_DATA_TYPE</B> int => unused
+	 *	<LI><B>SQL_DATETIME_SUB</B> int => unused
+	 *	<LI><B>CHAR_OCTET_LENGTH</B> int => for char types the
+	 *		 maximum number of bytes in the column
+	 *	<LI><B>ORDINAL_POSITION</B> int => index of column in table
+	 *		(starting at 1)
+	 *	<LI><B>IS_NULLABLE</B> String => "NO" means column definitely
+	 *		does not allow NULL values; "YES" means the column might
+	 *		allow NULL values.	An empty string means nobody knows.
+	 *	<LI><B>SCOPE_CATALOG</B> String => catalog of table that is the scope of a reference attribute (null if DATA_TYPE isn't REF)
+	 *	<LI><B>SCOPE_SCHEMA</B> String => schema of table that is the scope of a reference attribute (null if the DATA_TYPE isn't REF)
+	 *	<LI><B>SCOPE_TABLE</B> String => table name that this the scope of a reference attribute (null if the DATA_TYPE isn't REF)
+	 *	<LI><B>SOURCE_DATA_TYPE</B> short => source type of a distinct type or user-generated Ref type, SQL type from java.sql.Types (null if DATA_TYPE isn't DISTINCT or user-generated REF)
+	 *	<LI><B>IS_AUTOINCREMENT</B> String => Indicates whether this column is auto incremented
+	 *		<UL>
+	 *		<LI> YES --- if the column is auto incremented
+	 *		<LI> NO --- if the column is not auto incremented
+    	 *		<LI> empty string --- if it cannot be determined whether the column is auto incremented
+	 *		</UL>
+	 *	<LI><B>IS_GENERATEDCOLUMN</B> String => Indicates whether this is a generated column
+	 *		<UL>
+	 *		<LI> YES --- if this a generated column
+	 *		<LI> NO --- if this not a generated column
+	 *		<LI> empty string --- if it cannot be determined whether this is a generated column
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param catalog - a catalog name; must match the catalog name as it is stored in the database;
+	 *	"" retrieves those without a catalog; null means that the
+	 *	catalog name should not be used to narrow the search
+	 * @param schemaPattern - a schema name pattern; must match the schema name as it is stored
+	 *	in the database; "" retrieves those without a schema;
+	 *	null means that the schema name should not be used to narrow the search
+	 * @param tableNamePattern - a table name pattern; must match the table name as it is stored in the database
+	 *	For all tables this should be "%"
+	 * @param columnNamePattern - a column name pattern; must match the column name as it is stored in the database
+	 * @return ResultSet - each row is a column description
+	 * @throws SQLException if a database error occurs
+	 * @see #getSearchStringEscape
+	 */
+	@Override
+	public ResultSet getColumns(
+		String catalog,
+		String schemaPattern,
+		String tableNamePattern,
+		String columnNamePattern
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(2450);
+		query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", " +
+			"\"columns\".\"name\" AS \"COLUMN_NAME\", " +
+			"cast(").append(MonetDriver.getSQLTypeMap("\"columns\".\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
+			"\"columns\".\"type\" AS \"TYPE_NAME\", " +
+			"\"columns\".\"type_digits\" AS \"COLUMN_SIZE\", " +
+			"0 AS \"BUFFER_LENGTH\", " +
+			"\"columns\".\"type_scale\" AS \"DECIMAL_DIGITS\", " +
+			"cast(CASE WHEN \"columns\".\"type\" IN ('decimal', 'numeric', 'sec_interval') THEN 10 " +
+				"WHEN \"columns\".\"type\" IN ('int', 'smallint', 'tinyint', 'bigint', 'hugeint', 'float', 'real', 'double', 'oid', 'wrd') THEN 2 " +
+				"ELSE 0 END AS int) AS \"NUM_PREC_RADIX\", " +
+			"cast(CASE \"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable)
+			.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls).append(" END AS int) AS \"NULLABLE\", " +
+			"cast(null AS varchar(1)) AS \"REMARKS\", " +
+			"\"columns\".\"default\" AS \"COLUMN_DEF\", " +
+			"cast(0 as int) AS \"SQL_DATA_TYPE\", " +
+			"cast(0 as int) AS \"SQL_DATETIME_SUB\", " +
+			"cast(CASE WHEN \"columns\".\"type\" IN ('char','varchar','clob') THEN \"columns\".\"type_digits\" ELSE NULL END as int) AS \"CHAR_OCTET_LENGTH\", " +
+			"cast(\"columns\".\"number\" + 1 as int) AS \"ORDINAL_POSITION\", " +
+			"cast(CASE \"null\" WHEN true THEN 'YES' WHEN false THEN 'NO' ELSE '' END AS varchar(3)) AS \"IS_NULLABLE\", " +
+			"cast(null AS varchar(1)) AS \"SCOPE_CATALOG\", " +
+			"cast(null AS varchar(1)) AS \"SCOPE_SCHEMA\", " +
+			"cast(null AS varchar(1)) AS \"SCOPE_TABLE\", " +
+			"cast(").append(MonetDriver.getJavaType("other")).append(" AS smallint) AS \"SOURCE_DATA_TYPE\", " +
+			"cast(CASE WHEN \"columns\".\"default\" IS NOT NULL AND \"columns\".\"default\" LIKE 'next value for %' THEN 'YES' ELSE 'NO' END AS varchar(3)) AS \"IS_AUTOINCREMENT\", " +
+			"cast('NO' AS varchar(3)) AS \"IS_GENERATEDCOLUMN\" " +
+		"FROM \"sys\".\"columns\", " +
+			"\"sys\".\"tables\", " +
+			"\"sys\".\"schemas\" " +
+		"WHERE \"columns\".\"table_id\" = \"tables\".\"id\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\"");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (tableNamePattern != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(tableNamePattern));
+		}
+		if (columnNamePattern != null) {
+			query.append(" AND \"columns\".\"name\" ").append(composeMatchPart(columnNamePattern));
+		}
+
+		query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"ORDINAL_POSITION\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of the access rights for a table's columns.
+	 * MonetDB doesn't have this level of access rights.
+	 *
+	 * <P>Only privileges matching the column name criteria are
+	 * returned.  They are ordered by COLUMN_NAME and PRIVILEGE.
+	 *
+	 * <P>Each privilige description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => table catalog (may be null)
+	 *	<LI><B>TABLE_SCHEM</B> String => table schema (may be null)
+	 *	<LI><B>TABLE_NAME</B> String => table name
+	 *	<LI><B>COLUMN_NAME</B> String => column name
+	 *	<LI><B>GRANTOR</B> => grantor of access (may be null)
+	 *	<LI><B>GRANTEE</B> String => grantee of access
+	 *	<LI><B>PRIVILEGE</B> String => name of access (SELECT,
+	 *		INSERT, UPDATE, REFRENCES, ...)
+	 *	<LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted
+	 *		to grant to others; "NO" if not; null if unknown
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schemaPattern a schema name; "" retrieves those without a schema
+	 * @param tableNamePattern a table name
+	 * @param columnNamePattern a column name pattern
+	 * @return ResultSet each row is a column privilege description
+	 * @see #getSearchStringEscape
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getColumnPrivileges(
+		String catalog,
+		String schemaPattern,
+		String tableNamePattern,
+		String columnNamePattern
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(1100);
+		query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", " +
+			"\"columns\".\"name\" AS \"COLUMN_NAME\", " +
+			"\"grantors\".\"name\" AS \"GRANTOR\", " +
+			"\"grantees\".\"name\" AS \"GRANTEE\", " +
+			"CAST(CASE \"privileges\".\"privileges\" " +
+				"WHEN 1 THEN 'SELECT' " +
+				"WHEN 2 THEN 'UPDATE' " +
+				"WHEN 4 THEN 'INSERT' " +
+				"WHEN 8 THEN 'DELETE' " +
+				"WHEN 16 THEN 'EXECUTE' " +
+				"WHEN 32 THEN 'GRANT' " +
+				"ELSE NULL " +
+			"END AS varchar(7)) AS \"PRIVILEGE\", " +
+			"CAST(CASE \"privileges\".\"grantable\" " +
+				"WHEN 0 THEN 'NO' " +
+				"WHEN 1 THEN 'YES' " +
+				"ELSE NULL " +
+			"END AS varchar(3)) AS \"IS_GRANTABLE\" " +
+		"FROM \"sys\".\"privileges\", " +
+			"\"sys\".\"tables\", " +
+			"\"sys\".\"schemas\", " +
+			"\"sys\".\"columns\", " +
+			"\"sys\".\"auths\" AS \"grantors\", " +
+			"\"sys\".\"auths\" AS \"grantees\" " +
+		"WHERE \"privileges\".\"obj_id\" = \"columns\".\"id\" " +
+			"AND \"columns\".\"table_id\" = \"tables\".\"id\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\" " +
+			"AND \"privileges\".\"auth_id\" = \"grantees\".\"id\" " +
+			"AND \"privileges\".\"grantor\" = \"grantors\".\"id\"");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (tableNamePattern != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(tableNamePattern));
+		}
+		if (columnNamePattern != null) {
+			query.append(" AND \"columns\".\"name\" ").append(composeMatchPart(columnNamePattern));
+		}
+
+		query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"COLUMN_NAME\", \"PRIVILEGE\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of the access rights for each table available
+	 * in a catalog.
+	 *
+	 * <P>Only privileges matching the schema and table name
+	 * criteria are returned.  They are ordered by TABLE_SCHEM,
+	 * TABLE_NAME, and PRIVILEGE.
+	 *
+	 * <P>Each privilege description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => table catalog (may be null)
+	 *	<LI><B>TABLE_SCHEM</B> String => table schema (may be null)
+	 *	<LI><B>TABLE_NAME</B> String => table name
+	 *	<LI><B>GRANTOR</B> => grantor of access (may be null)
+	 *	<LI><B>GRANTEE</B> String => grantee of access
+	 *	<LI><B>PRIVILEGE</B> String => name of access (SELECT,
+	 *		INSERT, UPDATE, REFRENCES, ...)
+	 *	<LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted
+	 *		to grant to others; "NO" if not; null if unknown
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schemaPattern a schema name pattern; "" retrieves those without a schema
+	 * @param tableNamePattern a table name pattern
+	 * @return ResultSet each row is a table privilege description
+	 * @see #getSearchStringEscape
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getTablePrivileges(
+		String catalog,
+		String schemaPattern,
+		String tableNamePattern
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(1000);
+		query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", " +
+			"\"grantors\".\"name\" AS \"GRANTOR\", " +
+			"\"grantees\".\"name\" AS \"GRANTEE\", " +
+			"CAST(CASE \"privileges\".\"privileges\" " +
+				"WHEN 1 THEN 'SELECT' " +
+				"WHEN 2 THEN 'UPDATE' " +
+				"WHEN 4 THEN 'INSERT' " +
+				"WHEN 8 THEN 'DELETE' " +
+				"WHEN 16 THEN 'EXECUTE' " +
+				"WHEN 32 THEN 'GRANT' " +
+				"ELSE NULL " +
+			"END AS varchar(7)) AS \"PRIVILEGE\", " +
+			"CAST(CASE \"privileges\".\"grantable\" " +
+				"WHEN 0 THEN 'NO' " +
+				"WHEN 1 THEN 'YES' " +
+				"ELSE NULL " +
+			"END AS varchar(3)) AS \"IS_GRANTABLE\" " +
+		"FROM \"sys\".\"privileges\", " +
+			"\"sys\".\"tables\", " +
+			"\"sys\".\"schemas\", " +
+			"\"sys\".\"auths\" AS \"grantors\", " +
+			"\"sys\".\"auths\" AS \"grantees\" " +
+		"WHERE \"privileges\".\"obj_id\" = \"tables\".\"id\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\" " +
+			"AND \"privileges\".\"auth_id\" = \"grantees\".\"id\" " +
+			"AND \"privileges\".\"grantor\" = \"grantors\".\"id\"");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (tableNamePattern != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(tableNamePattern));
+		}
+
+		query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"PRIVILEGE\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of a table's optimal set of columns that
+	 * uniquely identifies a row. They are ordered by SCOPE.
+	 *
+	 * <P>Each column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>SCOPE</B> short => actual scope of result
+	 *		<UL>
+	 *		<LI> bestRowTemporary - very temporary, while using row
+	 *		<LI> bestRowTransaction - valid for remainder of current transaction
+	 *		<LI> bestRowSession - valid for remainder of current session
+	 *		</UL>
+	 *	<LI><B>COLUMN_NAME</B> String => column name
+	 *	<LI><B>DATA_TYPE</B> int => SQL data type from java.sql.Types
+	 *	<LI><B>TYPE_NAME</B> String => Data source dependent type name
+	 *	<LI><B>COLUMN_SIZE</B> int => precision
+	 *	<LI><B>BUFFER_LENGTH</B> int => not used
+	 *	<LI><B>DECIMAL_DIGITS</B> short  => scale
+	 *	<LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column
+	 *		like an Oracle ROWID
+	 *		<UL>
+	 *		<LI> bestRowUnknown - may or may not be pseudo column
+	 *		<LI> bestRowNotPseudo - is NOT a pseudo column
+	 *		<LI> bestRowPseudo - is a pseudo column
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name; "" retrieves those without a schema
+	 * @param table a table name
+	 * @param scope the scope of interest; use same values as SCOPE
+	 * @param nullable include columns that are nullable?
+	 * @return ResultSet each row is a column description
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getBestRowIdentifier(
+		String catalog,
+		String schema,
+		String table,
+		int scope,
+		boolean nullable
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(1500);
+		query.append("SELECT CAST(").append(DatabaseMetaData.bestRowSession).append(" AS smallint) AS \"SCOPE\", " +
+			"\"columns\".\"name\" AS \"COLUMN_NAME\", " +
+			"cast(").append(MonetDriver.getSQLTypeMap("\"columns\".\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
+			"\"columns\".\"type\" AS \"TYPE_NAME\", " +
+			"\"columns\".\"type_digits\" AS \"COLUMN_SIZE\", " +
+			"CAST(0 as int) AS \"BUFFER_LENGTH\", " +
+			"CAST(\"columns\".\"type_scale\" AS smallint) AS \"DECIMAL_DIGITS\", " +
+			"CAST(").append(DatabaseMetaData.bestRowNotPseudo).append(" AS smallint) AS \"PSEUDO_COLUMN\" " +
+		"FROM \"sys\".\"keys\", " +
+			"\"sys\".\"objects\", " +
+			"\"sys\".\"columns\", " +
+			"\"sys\".\"tables\", " +
+			"\"sys\".\"schemas\" " +
+		"WHERE \"keys\".\"id\" = \"objects\".\"id\" " +
+			"AND \"keys\".\"table_id\" = \"tables\".\"id\" " +
+			"AND \"keys\".\"table_id\" = \"columns\".\"table_id\" " +
+			"AND \"objects\".\"name\" = \"columns\".\"name\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\" " +
+			"AND \"keys\".\"type\" IN (0, 1)");	// only primary keys (type = 0) and unique keys (type = 1), not fkeys (type = 2)
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schema != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schema));
+		}
+		if (table != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(table));
+		}
+		if (scope != DatabaseMetaData.bestRowSession && scope != DatabaseMetaData.bestRowTransaction && scope != DatabaseMetaData.bestRowTemporary) {
+			query.append(" AND 1 = 0");
+		}
+		if (!nullable) {
+			query.append(" AND \"columns\".\"null\" = false");
+		}
+
+		query.append(" ORDER BY \"keys\".\"type\", \"columns\".\"name\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of a table's columns that are automatically
+	 * updated when any value in a row is updated. They are unordered.
+	 *
+	 * <P>Each column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>SCOPE</B> short => is not used
+	 *	<LI><B>COLUMN_NAME</B> String => column name
+	 *	<LI><B>DATA_TYPE</B> int => SQL data type from java.sql.Types
+	 *	<LI><B>TYPE_NAME</B> String => Data source dependent type name
+	 *	<LI><B>COLUMN_SIZE</B> int => precision
+	 *	<LI><B>BUFFER_LENGTH</B> int => length of column value in bytes
+	 *	<LI><B>DECIMAL_DIGITS</B> short => scale
+	 *	<LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column like an Oracle ROWID
+	 *		<UL>
+	 *		<LI> versionColumnUnknown - may or may not be pseudo column
+	 *		<LI> versionColumnNotPseudo - is NOT a pseudo column
+	 *		<LI> versionColumnPseudo - is a pseudo column
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name; "" retrieves those without a schema
+	 * @param table a table name
+	 * @return ResultSet each row is a column description
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getVersionColumns(
+		String catalog,
+		String schema,
+		String table
+	) throws SQLException
+	{
+		// MonetDB currently does not have columns which update themselves, so return an empty ResultSet
+		String query =
+		"SELECT CAST(0 as smallint) AS \"SCOPE\", " +
+			"CAST(null as varchar(1)) AS \"COLUMN_NAME\", " +
+			"CAST(0 as int) AS \"DATA_TYPE\", " +
+			"CAST(null as varchar(1)) AS \"TYPE_NAME\", " +
+			"CAST(0 as int) AS \"COLUMN_SIZE\", " +
+			"CAST(0 as int) AS \"BUFFER_LENGTH\", " +
+			"CAST(0 as smallint) AS \"DECIMAL_DIGITS\", " +
+			"CAST(0 as smallint) AS \"PSEUDO_COLUMN\" " +
+		"WHERE 1 = 0";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Get a description of a table's primary key columns.  They
+	 * are ordered by COLUMN_NAME.
+	 *
+	 * <P>Each column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => table catalog (may be null)
+	 *	<LI><B>TABLE_SCHEM</B> String => table schema (may be null)
+	 *	<LI><B>TABLE_NAME</B> String => table name
+	 *	<LI><B>COLUMN_NAME</B> String => column name
+	 *	<LI><B>KEY_SEQ</B> short => sequence number within primary key
+	 *	<LI><B>PK_NAME</B> String => primary key name (may be null)
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name pattern; "" retrieves those
+	 * without a schema
+	 * @param table a table name
+	 * @return ResultSet each row is a primary key column description
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getPrimaryKeys(
+		String catalog,
+		String schema,
+		String table
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(600);
+		query.append("SELECT cast(null AS varchar(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", " +
+			"\"objects\".\"name\" AS \"COLUMN_NAME\", " +
+			"CAST(1 + \"objects\".\"nr\" AS smallint) AS \"KEY_SEQ\", " +
+			" \"keys\".\"name\" AS \"PK_NAME\" " +
+		"FROM \"sys\".\"keys\", " +
+			"\"sys\".\"objects\", " +
+			"\"sys\".\"tables\", " +
+			"\"sys\".\"schemas\" " +
+		"WHERE \"keys\".\"id\" = \"objects\".\"id\" " +
+			"AND \"keys\".\"table_id\" = \"tables\".\"id\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\" " +
+			"AND \"keys\".\"type\" = 0");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schema != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schema));
+		}
+		if (table != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(table));
+		}
+
+		query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"COLUMN_NAME\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+
+	private final static String keyQuery =
+	"SELECT cast(null AS varchar(1)) AS \"PKTABLE_CAT\", " +
+		"\"pkschema\".\"name\" AS \"PKTABLE_SCHEM\", " +
+		"\"pktable\".\"name\" AS \"PKTABLE_NAME\", " +
+		"\"pkkeycol\".\"name\" AS \"PKCOLUMN_NAME\", " +
+		"cast(null AS varchar(1)) AS \"FKTABLE_CAT\", " +
+		"\"fkschema\".\"name\" AS \"FKTABLE_SCHEM\", " +
+		"\"fktable\".\"name\" AS \"FKTABLE_NAME\", " +
+		"\"fkkeycol\".\"name\" AS \"FKCOLUMN_NAME\", " +
+		"CAST(1 + \"pkkeycol\".\"nr\" AS smallint) AS \"KEY_SEQ\", " +
+		DatabaseMetaData.importedKeyNoAction + " AS \"UPDATE_RULE\", " +
+		DatabaseMetaData.importedKeyNoAction + " AS \"DELETE_RULE\", " +
+		"\"fkkey\".\"name\" AS \"FK_NAME\", " +
+		"\"pkkey\".\"name\" AS \"PK_NAME\", " +
+		DatabaseMetaData.importedKeyNotDeferrable + " AS \"DEFERRABILITY\" " +
+	"FROM \"sys\".\"keys\" AS \"fkkey\", " +
+		"\"sys\".\"keys\" AS \"pkkey\", " +
+		"\"sys\".\"objects\" AS \"fkkeycol\", " +
+		"\"sys\".\"objects\" AS \"pkkeycol\", " +
+		"\"sys\".\"tables\" AS \"fktable\", " +
+		"\"sys\".\"tables\" AS \"pktable\", " +
+		"\"sys\".\"schemas\" AS \"fkschema\", " +
+		"\"sys\".\"schemas\" AS \"pkschema\" " +
+	"WHERE \"fktable\".\"id\" = \"fkkey\".\"table_id\"" +
+	" AND \"pktable\".\"id\" = \"pkkey\".\"table_id\"" +
+	" AND \"fkkey\".\"id\" = \"fkkeycol\".\"id\"" +
+	" AND \"pkkey\".\"id\" = \"pkkeycol\".\"id\"" +
+	" AND \"fkschema\".\"id\" = \"fktable\".\"schema_id\"" +
+	" AND \"pkschema\".\"id\" = \"pktable\".\"schema_id\"" +
+	" AND \"fkkey\".\"rkey\" > -1" +
+	" AND \"fkkey\".\"rkey\" = \"pkkey\".\"id\"" +
+	" AND \"fkkeycol\".\"nr\" = \"pkkeycol\".\"nr\"";
+
+	/**
+	 * Get a description of the primary key columns that are
+	 * referenced by a table's foreign key columns (the primary keys
+	 * imported by a table). They are ordered by PKTABLE_CAT,
+	 * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ.
+	 *
+	 * <P>Each primary key column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>PKTABLE_CAT</B> String => primary key table catalog
+	 *		being imported (may be null)
+	 *	<LI><B>PKTABLE_SCHEM</B> String => primary key table schema
+	 *		being imported (may be null)
+	 *	<LI><B>PKTABLE_NAME</B> String => primary key table name
+	 *		being imported
+	 *	<LI><B>PKCOLUMN_NAME</B> String => primary key column name
+	 *		being imported
+	 *	<LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null)
+	 *	<LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null)
+	 *	<LI><B>FKTABLE_NAME</B> String => foreign key table name
+	 *	<LI><B>FKCOLUMN_NAME</B> String => foreign key column name
+	 *	<LI><B>KEY_SEQ</B> short => sequence number within foreign key
+	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
+	 *	<LI><B>UPDATE_RULE</B> short => What happens to
+	 *		 foreign key when primary is updated:
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
+	 *		<LI> importedKeyCascade - change imported key to agree
+	 *				 with primary key update
+	 *		<LI> importedKeyRestrict - do not allow update of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been updated
+	 *		</UL>
+	 *	<LI><B>DELETE_RULE</B> short => What happens to
+	 *		the foreign key when primary is deleted.
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
+	 *		<LI> importedKeyCascade - delete rows that import a deleted key
+	 *		<LI> importedKeyRestrict - do not allow delete of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been deleted
+	 *		</UL>
+	 *	<LI><B>FK_NAME</B> String => foreign key name (may be null)
+	 *	<LI><B>PK_NAME</B> String => primary key name (may be null)
+	 *	<LI><B>DEFERRABILITY</B> short => can the evaluation of foreign key constraints be deferred until commit
+	 *		<UL>
+	 *		<LI> importedKeyInitiallyDeferred - see SQL92 for definition
+	 *		<LI> importedKeyInitiallyImmediate - see SQL92 for definition
+	 *		<LI> importedKeyNotDeferrable - see SQL92 for definition
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name pattern; "" retrieves those without a schema
+	 * @param table a table name
+	 * @return ResultSet each row is a primary key column description
+	 * @see #getExportedKeys
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getImportedKeys(String catalog, String schema, String table)
+		throws SQLException
+	{
+		StringBuilder query = new StringBuilder(keyQuery.length() + 250);
+		query.append(keyQuery);
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schema != null) {
+			query.append(" AND \"fkschema\".\"name\" ").append(composeMatchPart(schema));
+		}
+		if (table != null) {
+			query.append(" AND \"fktable\".\"name\" ").append(composeMatchPart(table));
+		}
+
+		query.append(" ORDER BY \"PKTABLE_SCHEM\", \"PKTABLE_NAME\", \"PK_NAME\", \"KEY_SEQ\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of a foreign key columns that reference a
+	 * table's primary key columns (the foreign keys exported by a table).
+	 * They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and KEY_SEQ.
+	 *
+	 * <P>Each foreign key column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null)
+	 *	<LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null)
+	 *	<LI><B>PKTABLE_NAME</B> String => primary key table name
+	 *	<LI><B>PKCOLUMN_NAME</B> String => primary key column name
+	 *	<LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null)
+	 *		being exported (may be null)
+	 *	<LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null)
+	 *		being exported (may be null)
+	 *	<LI><B>FKTABLE_NAME</B> String => foreign key table name
+	 *		being exported
+	 *	<LI><B>FKCOLUMN_NAME</B> String => foreign key column name
+	 *		being exported
+	 *	<LI><B>KEY_SEQ</B> short => sequence number within foreign key
+	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
+	 *	<LI><B>UPDATE_RULE</B> short => What happens to
+	 *		 foreign key when primary is updated:
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
+	 *		<LI> importedKeyCascade - change imported key to agree
+	 *				 with primary key update
+	 *		<LI> importedKeyRestrict - do not allow update of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been updated
+	 *		</UL>
+	 *	<LI><B>DELETE_RULE</B> short => What happens to
+	 *		the foreign key when primary is deleted.
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
+	 *		<LI> importedKeyCascade - delete rows that import a deleted key
+	 *		<LI> importedKeyRestrict - do not allow delete of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been deleted
+	 *		</UL>
+	 *	<LI><B>FK_NAME</B> String => foreign key identifier (may be null)
+	 *	<LI><B>PK_NAME</B> String => primary key identifier (may be null)
+	 *	<LI><B>DEFERRABILITY</B> short => can the evaluation of foreign key constraints be deferred until commit
+	 *		<UL>
+	 *		<LI> importedKeyInitiallyDeferred - see SQL92 for definition
+	 *		<LI> importedKeyInitiallyImmediate - see SQL92 for definition
+	 *		<LI> importedKeyNotDeferrable - see SQL92 for definition
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name pattern; "" retrieves those without a schema
+	 * @param table a table name
+	 * @return ResultSet each row is a foreign key column description
+	 * @see #getImportedKeys
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getExportedKeys(String catalog, String schema, String table)
+		throws SQLException
+	{
+		StringBuilder query = new StringBuilder(keyQuery.length() + 250);
+		query.append(keyQuery);
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schema != null) {
+			query.append(" AND \"pkschema\".\"name\" ").append(composeMatchPart(schema));
+		}
+		if (table != null) {
+			query.append(" AND \"pktable\".\"name\" ").append(composeMatchPart(table));
+		}
+
+		query.append(" ORDER BY \"FKTABLE_SCHEM\", \"FKTABLE_NAME\", \"FK_NAME\", \"KEY_SEQ\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of the foreign key columns in the foreign key
+	 * table that reference the primary key columns of the primary key
+	 * table. (describe how one table imports another's key) This
+	 * should normally return a single foreign key/primary key pair
+	 * (most tables only import a foreign key from a table once.)
+	 * They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and KEY_SEQ.
+	 *
+	 * <P>Each foreign key column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null)
+	 *	<LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null)
+	 *	<LI><B>PKTABLE_NAME</B> String => primary key table name
+	 *	<LI><B>PKCOLUMN_NAME</B> String => primary key column name
+	 *	<LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null)
+	 *		being exported (may be null)
+	 *	<LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null)
+	 *		being exported (may be null)
+	 *	<LI><B>FKTABLE_NAME</B> String => foreign key table name
+	 *		being exported
+	 *	<LI><B>FKCOLUMN_NAME</B> String => foreign key column name
+	 *		being exported
+	 *	<LI><B>KEY_SEQ</B> short => sequence number within foreign key
+	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
+	 *	<LI><B>UPDATE_RULE</B> short => What happens to
+	 *		 foreign key when primary is updated:
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
+	 *		<LI> importedKeyCascade - change imported key to agree
+	 *				 with primary key update
+	 *		<LI> importedKeyRestrict - do not allow update of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been updated
+	 *		</UL>
+	 *	<LI><B>DELETE_RULE</B> short => What happens to
+	 *		the foreign key when primary is deleted.
+	 *		<UL>
+	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
+	 *		<LI> importedKeyCascade - delete rows that import a deleted key
+	 *		<LI> importedKeyRestrict - do not allow delete of primary
+	 *				 key if it has been imported
+	 *		<LI> importedKeySetNull - change imported key to NULL if
+	 *				 its primary key has been deleted
+	 *		</UL>
+	 *	<LI><B>FK_NAME</B> String => foreign key identifier (may be null)
+	 *	<LI><B>PK_NAME</B> String => primary key identifier (may be null)
+	 *	<LI><B>DEFERRABILITY</B> short => can the evaluation of foreign key constraints be deferred until commit
+	 *		<UL>
+	 *		<LI> importedKeyInitiallyDeferred - see SQL92 for definition
+	 *		<LI> importedKeyInitiallyImmediate - see SQL92 for definition
+	 *		<LI> importedKeyNotDeferrable - see SQL92 for definition
+	 *		</UL>
+	 *	</OL>
+	 *
+	 * @param pcatalog primary key catalog name; "" retrieves those without a catalog
+	 * @param pschema primary key schema name pattern; "" retrieves those without a schema
+	 * @param ptable primary key table name
+	 * @param fcatalog foreign key catalog name; "" retrieves those without a catalog
+	 * @param fschema foreign key schema name pattern; "" retrieves those without a schema
+	 * @param ftable koreign key table name
+	 * @return ResultSet each row is a foreign key column description
+	 * @throws SQLException if a database error occurs
+	 * @see #getImportedKeys
+	 */
+	@Override
+	public ResultSet getCrossReference(
+		String pcatalog,
+		String pschema,
+		String ptable,
+		String fcatalog,
+		String fschema,
+		String ftable
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(keyQuery.length() + 350);
+		query.append(keyQuery);
+
+		if (pcatalog != null && pcatalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (pschema != null) {
+			query.append(" AND \"pkschema\".\"name\" ").append(composeMatchPart(pschema));
+		}
+		if (ptable != null) {
+			query.append(" AND \"pktable\".\"name\" ").append(composeMatchPart(ptable));
+		}
+
+		if (fcatalog != null && fcatalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (fschema != null) {
+			query.append(" AND \"fkschema\".\"name\" ").append(composeMatchPart(fschema));
+		}
+		if (ftable != null) {
+			query.append(" AND \"fktable\".\"name\" ").append(composeMatchPart(ftable));
+		}
+
+		query.append(" ORDER BY \"FKTABLE_SCHEM\", \"FKTABLE_NAME\", \"FK_NAME\", \"KEY_SEQ\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Get a description of all the SQL data types supported by
+	 * this database. They are ordered by DATA_TYPE and then by how
+	 * closely the data type maps to the corresponding JDBC SQL type.
+	 *
+	 * If the database supports SQL distinct types, then getTypeInfo() will
+	 * return a single row with a TYPE_NAME of DISTINCT and a DATA_TYPE of Types.DISTINCT.
+	 * If the database supports SQL structured types, then getTypeInfo() will
+	 * return a single row with a TYPE_NAME of STRUCT and a DATA_TYPE of Types.STRUCT.
+	 * If SQL distinct or structured types are supported, then information on
+	 * the individual types may be obtained from the getUDTs() method.
+	 *
+	 * <P>Each type description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TYPE_NAME</B> String => Type name
+	 *	<LI><B>DATA_TYPE</B> int => SQL data type from java.sql.Types
+	 *	<LI><B>PRECISION</B> int => maximum precision
+	 *	<LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal (may be null)
+	 *	<LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal (may be null)
+	 *	<LI><B>CREATE_PARAMS</B> String => parameters used in creating
+	 *		the type (may be null)
+	 *	<LI><B>NULLABLE</B> short => can you use NULL for this type?
+	 *		<UL>
+	 *		<LI> typeNoNulls - does not allow NULL values
+	 *		<LI> typeNullable - allows NULL values
+	 *		<LI> typeNullableUnknown - nullability unknown
+	 *		</UL>
+	 *	<LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive?
+	 *	<LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type:
+	 *		<UL>
+	 *		<LI> typePredNone - No support
+	 *		<LI> typePredChar - Only supported with WHERE .. LIKE
+	 *		<LI> typePredBasic - Supported except for WHERE .. LIKE
+	 *		<LI> typeSearchable - Supported for all WHERE ..
+	 *		</UL>
+	 *	<LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned?
+	 *	<LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value?
+	 *	<LI><B>AUTO_INCREMENT</B> boolean => can it be used for an
+	 *		auto-increment value?
+	 *	<LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name
+	 *		(may be null)
+	 *	<LI><B>MINIMUM_SCALE</B> short => minimum scale supported
+	 *	<LI><B>MAXIMUM_SCALE</B> short => maximum scale supported
+	 *	<LI><B>SQL_DATA_TYPE</B> int => unused
+	 *	<LI><B>SQL_DATETIME_SUB</B> int => unused
+	 *	<LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10
+	 *	</OL>
+	 *
+	 * @return ResultSet each row is a SQL type description
+	 * @throws Exception if the developer made a Boo-Boo
+	 */
+	@Override
+	public ResultSet getTypeInfo() throws SQLException {
+		StringBuilder query = new StringBuilder(2300);
+		query.append("SELECT \"sqlname\" AS \"TYPE_NAME\", " +
+			"cast(").append(MonetDriver.getSQLTypeMap("\"sqlname\"")).append(" AS int) AS \"DATA_TYPE\", " +
+			"\"digits\" AS \"PRECISION\", " +	// note that when radix is 2 the precision shows the number of bits
+			"cast(CASE WHEN \"systemname\" IN ('str', 'inet', 'json', 'url', 'uuid') THEN ''''" +
+				" ELSE NULL END AS varchar(2)) AS \"LITERAL_PREFIX\", " +
+			"cast(CASE WHEN \"systemname\" IN ('str', 'inet', 'json', 'url', 'uuid') THEN ''''" +
+				" ELSE NULL END AS varchar(2)) AS \"LITERAL_SUFFIX\", " +
+			"CASE WHEN \"sqlname\" IN ('char', 'varchar') THEN 'max length'" +
+				" WHEN \"sqlname\" = 'decimal' THEN 'precision, scale'" +
+				" WHEN \"sqlname\" IN ('time', 'timetz', 'timestamp', 'timestamptz', 'sec_interval') THEN 'precision'" +
+				" ELSE NULL END AS \"CREATE_PARAMS\", " +
+			"cast(CASE WHEN \"systemname\" = 'oid' THEN ").append(DatabaseMetaData.typeNoNulls)
+				.append(" ELSE ").append(DatabaseMetaData.typeNullable).append(" END AS smallint) AS \"NULLABLE\", " +
+			"CASE WHEN \"systemname\" IN ('str', 'json', 'url') THEN true ELSE false END AS \"CASE_SENSITIVE\", " +
+			"cast(CASE \"systemname\" WHEN 'table' THEN ").append(DatabaseMetaData.typePredNone)
+				.append(" WHEN 'str' THEN ").append(DatabaseMetaData.typePredChar)
+				.append(" WHEN 'sqlblob' THEN ").append(DatabaseMetaData.typePredChar)
+				.append(" ELSE ").append(DatabaseMetaData.typePredBasic).append(" END AS smallint) AS \"SEARCHABLE\", " +
+			"CASE WHEN \"sqlname\" IN ('tinyint','smallint','int','bigint','hugeint','decimal','real','double','sec_interval','month_interval') THEN false ELSE true END AS \"UNSIGNED_ATTRIBUTE\", " +
+			"CASE \"sqlname\" WHEN 'decimal' THEN true ELSE false END AS \"FIXED_PREC_SCALE\", " +
+			"CASE WHEN \"sqlname\" IN ('tinyint','smallint','int','bigint','hugeint','decimal','oid','wrd') THEN true ELSE false END AS \"AUTO_INCREMENT\", " +
+			"\"systemname\" AS \"LOCAL_TYPE_NAME\", " +
+			"cast(0 AS smallint) AS \"MINIMUM_SCALE\", " +
+			"cast(CASE WHEN \"sqlname\" = 'decimal' THEN (CASE \"systemname\" WHEN 'lng' THEN 18 WHEN 'hge' THEN 38 WHEN 'int' THEN 9 WHEN 'sht' THEN 4 WHEN 'bte' THEN 2 ELSE 0 END)" +
+				" WHEN \"sqlname\" IN ('sec_interval', 'timestamp', 'timestamptz') THEN 9 ELSE 0 END AS smallint) AS \"MAXIMUM_SCALE\", " +
+			"cast(0 AS int) AS \"SQL_DATA_TYPE\", " +
+			"cast(0 AS int) AS \"SQL_DATETIME_SUB\", " +
+			"cast(\"radix\" as int) AS \"NUM_PREC_RADIX\" " +
+		"FROM \"sys\".\"types\" " +
+		"ORDER BY \"DATA_TYPE\", \"sqlname\", \"id\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Retrieves a description of the given table's indices and statistics.
+	 * They are ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION.
+	 *
+	 * <P>Each index column description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => table catalog (may be null)
+	 *	<LI><B>TABLE_SCHEM</B> String => table schema (may be null)
+	 *	<LI><B>TABLE_NAME</B> String => table name
+	 *	<LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique?
+	 *		false when TYPE is tableIndexStatistic
+	 *	<LI><B>INDEX_QUALIFIER</B> String => index catalog (may be null);
+	 *		null when TYPE is tableIndexStatistic
+	 *	<LI><B>INDEX_NAME</B> String => index name; null when TYPE is
+	 *		tableIndexStatistic
+	 *	<LI><B>TYPE</B> short => index type:
+	 *		<UL>
+	 *		<LI> tableIndexStatistic - this identifies table statistics that are
+	 *			 returned in conjuction with a table's index descriptions
+	 *		<LI> tableIndexClustered - this is a clustered index
+	 *		<LI> tableIndexHashed - this is a hashed index
+	 *		<LI> tableIndexOther - this is some other style of index
+	 *		</UL>
+	 *	<LI><B>ORDINAL_POSITION</B> short => column sequence number
+	 *		within index; zero when TYPE is tableIndexStatistic
+	 *	<LI><B>COLUMN_NAME</B> String => column name; null when TYPE is
+	 *		tableIndexStatistic
+	 *	<LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending
+	 *		"D" => descending, may be null if sort sequence is not supported;
+	 *		null when TYPE is tableIndexStatistic
+	 *	<LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then
+	 *		this is the number of rows in the table; otherwise it is the
+	 *		number of unique values in the index.
+	 *	<LI><B>PAGES</B> int => When TYPE is  tableIndexStatisic then
+	 *		this is the number of pages used for the table, otherwise it
+	 *		is the number of pages used for the current index.
+	 *	<LI><B>FILTER_CONDITION</B> String => Filter condition, if any.
+	 *		(may be null)
+	 *	</OL>
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog
+	 * @param schema a schema name pattern; "" retrieves those without a schema
+	 * @param table a table name
+	 * @param unique when true, return only indices for unique values;
+	 *	   when false, return indices regardless of whether unique or not
+	 * @param approximate when true, result is allowed to reflect approximate
+	 *	   or out of data values; when false, results are requested to be
+	 *	   accurate
+	 * @return ResultSet each row is an index column description
+	 * @throws SQLException if a database occurs
+	 */
+	@Override
+	public ResultSet getIndexInfo(
+		String catalog,
+		String schema,
+		String table,
+		boolean unique,
+		boolean approximate
+	) throws SQLException
+	{
+		String table_row_count = "0";
+
+		if (!approximate && schema != null && table != null && schema.length() > 0 && table.length() > 0) {
+			// we need the exact cardinality for one specific fully qualified table
+			ResultSet count = null;
+			try {
+				count = executeMetaDataQuery("SELECT COUNT(*) FROM \"" + schema + "\".\"" + table + "\"");
+				if (count != null && count.next()) {
+					String count_value = count.getString(1);
+					if (count_value != null && count_value.length() > 0)
+						table_row_count = count_value;
+				}
+			} catch (SQLException e) {
+				// ignore
+			} finally {
+				if (count != null) {
+					try {
+						count.close();
+					} catch (SQLException e) { /* ignore */ }
+				}
+			}
+		}
+
+		StringBuilder query = new StringBuilder(1250);
+		query.append(
+		"SELECT CAST(null AS char(1)) AS \"TABLE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TABLE_SCHEM\", " +
+			"\"tables\".\"name\" AS \"TABLE_NAME\", " +
+			"CASE WHEN \"keys\".\"name\" IS NULL THEN true ELSE false END AS \"NON_UNIQUE\", " +
+			"CAST(null AS varchar(1)) AS \"INDEX_QUALIFIER\", " +
+			"\"idxs\".\"name\" AS \"INDEX_NAME\", " +
+			"CASE \"idxs\".\"type\" WHEN 0 THEN ").append(DatabaseMetaData.tableIndexHashed).append(" ELSE ").append(DatabaseMetaData.tableIndexOther).append(" END AS \"TYPE\", " +
+			"CAST(\"objects\".\"nr\" +1 AS smallint) AS \"ORDINAL_POSITION\", "+
+			"\"columns\".\"name\" AS \"COLUMN_NAME\", " +
+			"CAST(null AS varchar(1)) AS \"ASC_OR_DESC\", " +	// sort sequence currently not supported in keys or indexes in MonetDB
+			"CAST(").append(table_row_count).append(" AS int) AS \"CARDINALITY\", " +
+			"CAST(0 AS int) AS \"PAGES\", " +
+			"CAST(null AS varchar(1)) AS \"FILTER_CONDITION\" " +
+		"FROM \"sys\".\"idxs\" LEFT JOIN \"sys\".\"keys\" ON \"idxs\".\"name\" = \"keys\".\"name\", " +
+			"\"sys\".\"schemas\", " +
+			"\"sys\".\"objects\", " +
+			"\"sys\".\"columns\", " +
+			"\"sys\".\"tables\" " +
+		"WHERE \"idxs\".\"table_id\" = \"tables\".\"id\" " +
+			"AND \"tables\".\"schema_id\" = \"schemas\".\"id\" " +
+			"AND \"idxs\".\"id\" = \"objects\".\"id\" " +
+			"AND \"tables\".\"id\" = \"columns\".\"table_id\" " +
+			"AND \"objects\".\"name\" = \"columns\".\"name\" " +
+			"AND (\"keys\".\"type\" IS NULL OR \"keys\".\"type\" = 1)");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schema != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schema));
+		}
+		if (table != null) {
+			query.append(" AND \"tables\".\"name\" ").append(composeMatchPart(table));
+		}
+		if (unique) {
+			query.append(" AND \"keys\".\"name\" IS NOT NULL");
+		}
+		query.append(" ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	//== 1.2 methods (JDBC 2)
+
+	/**
+	 * Does the database support the given result set type?
+	 *
+	 * @param type - defined in java.sql.ResultSet
+	 * @return true if so; false otherwise
+	 * @throws SQLException - if a database access error occurs
+	 */
+	@Override
+	public boolean supportsResultSetType(int type) throws SQLException {
+		// The only type we don't support
+		return type != ResultSet.TYPE_SCROLL_SENSITIVE;
+	}
+
+
+	/**
+	 * Does the database support the concurrency type in combination
+	 * with the given result set type?
+	 *
+	 * @param type - defined in java.sql.ResultSet
+	 * @param concurrency - type defined in java.sql.ResultSet
+	 * @return true if so; false otherwise
+	 * @throws SQLException - if a database access error occurs
+	*/
+	@Override
+	public boolean supportsResultSetConcurrency(int type, int concurrency)
+		throws SQLException
+	{
+		// These combinations are not supported!
+		if (type == ResultSet.TYPE_SCROLL_SENSITIVE)
+			return false;
+
+		// We do only support Read Only ResultSets
+		if (concurrency != ResultSet.CONCUR_READ_ONLY)
+			return false;
+
+		// Everything else we do (well, what's left of it :) )
+		return true;
+	}
+
+
+	/* lots of unsupported stuff... (no updatable ResultSet!) */
+	@Override
+	public boolean ownUpdatesAreVisible(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean ownDeletesAreVisible(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean ownInsertsAreVisible(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean othersUpdatesAreVisible(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean othersDeletesAreVisible(int i) {
+		return false;
+	}
+
+	@Override
+	public boolean othersInsertsAreVisible(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean updatesAreDetected(int type) {
+		return false;
+	}
+
+	@Override
+	public boolean deletesAreDetected(int i) {
+		return false;
+	}
+
+	@Override
+	public boolean insertsAreDetected(int type) {
+		return false;
+	}
+
+	/**
+	 * Indicates whether the driver supports batch updates.
+	 */
+	@Override
+	public boolean supportsBatchUpdates() {
+		return true;
+	}
+
+	/**
+	 * Retrieves a description of the user-defined types (UDTs) defined in a particular schema.
+	 * Schema-specific UDTs may have type JAVA_OBJECT, STRUCT, or DISTINCT.
+	 * Only types matching the catalog, schema, type name and type criteria are returned.
+	 * They are ordered by DATA_TYPE, TYPE_CAT, TYPE_SCHEM and TYPE_NAME.
+	 * The type name parameter may be a fully-qualified name. In this case, the catalog and schemaPattern parameters are ignored.
+	 *
+	 * Each type description has the following columns:
+	 *
+	 * 1 TYPE_CAT String => the type's catalog (may be null)
+	 * 2 TYPE_SCHEM String => type's schema (may be null)
+	 * 3 TYPE_NAME String => type name
+	 * 4 CLASS_NAME String => Java class name
+	 * 5 DATA_TYPE int => type value defined in java.sql.Types. One of JAVA_OBJECT, STRUCT, or DISTINCT
+	 * 6 REMARKS String => explanatory comment on the type
+	 * 7 BASE_TYPE short => type code of the source type of a DISTINCT type or the type that implements the
+	 *   user-generated reference type of the SELF_REFERENCING_COLUMN of a structured type as defined
+	 *   in java.sql.Types (null if DATA_TYPE is not DISTINCT or not STRUCT with REFERENCE_GENERATION = USER_DEFINED)
+	 *
+	 * @throws SQLException
+	 */
+	@Override
+	public ResultSet getUDTs(
+		String catalog,
+		String schemaPattern,
+		String typeNamePattern,
+		int[] types
+	) throws SQLException
+	{
+		StringBuilder query = new StringBuilder(990);
+		if (types != null && types.length > 0) {
+			query.append("SELECT * FROM (");
+		}
+		query.append("SELECT cast(null as char(1)) AS \"TYPE_CAT\", " +
+			"\"schemas\".\"name\" AS \"TYPE_SCHEM\", " +
+			"\"types\".\"sqlname\" AS \"TYPE_NAME\", " +
+			"CASE \"types\".\"sqlname\"" +
+				// next 4 UDTs are known
+				" WHEN 'inet' THEN 'nl.cwi.monetdb.jdbc.types.INET'" +
+				" WHEN 'json' THEN 'java.lang.String'" +
+				" WHEN 'url'  THEN 'nl.cwi.monetdb.jdbc.types.URL'" +
+				" WHEN 'uuid' THEN 'java.lang.String'" +
+				" ELSE 'java.lang.Object' END AS \"CLASS_NAME\", " +
+			"CAST(CASE WHEN \"types\".\"sqlname\" IN ('inet', 'json', 'url', 'uuid') THEN ").append(Types.JAVA_OBJECT)
+				.append(" ELSE ").append(Types.STRUCT).append(" END AS int) AS \"DATA_TYPE\", " +
+			"\"types\".\"systemname\" AS \"REMARKS\", " +
+			"cast(null as smallint) AS \"BASE_TYPE\" " +
+			"FROM \"sys\".\"types\" JOIN \"sys\".\"schemas\" ON \"types\".\"schema_id\" = \"schemas\".\"id\" " +
+			// exclude the built-in types (I assume they always have id <= 99 and eclass < 15)
+			"WHERE \"types\".\"id\" > 99 AND \"types\".\"eclass\" >= 15");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (typeNamePattern != null) {
+			query.append(" AND \"types\".\"sqlname\" ").append(composeMatchPart(typeNamePattern));
+		}
+		if (types != null && types.length > 0) {
+			query.append(") AS getUDTs WHERE \"DATA_TYPE\" IN (");
+			for (int i = 0; i < types.length; i++) {
+				if (i > 0) {
+					query.append(", ");
+				}
+				query.append(types[i]);
+			}
+			query.append(")");
+		}
+		query.append(" ORDER BY \"DATA_TYPE\", \"TYPE_SCHEM\", \"TYPE_NAME\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+
+	/**
+	 * Retrieves the connection that produced this metadata object.
+	 *
+	 * @return the connection that produced this metadata object
+	 */
+	@Override
+	public Connection getConnection() {
+		return con;
+	}
+
+	/* I don't find these in the spec!?! */
+	public boolean rowChangesAreDetected(int type) {
+		return false;
+	}
+
+	public boolean rowChangesAreVisible(int type) {
+		return false;
+	}
+
+	//== 1.4 methods (JDBC 3)
+
+	/**
+	 * Retrieves whether this database supports savepoints.
+	 *
+	 * @return <code>true</code> if savepoints are supported;
+	 *		   <code>false</code> otherwise
+	 */
+	@Override
+	public boolean supportsSavepoints() {
+		return true;
+	}
+
+	/**
+	 * Retrieves whether this database supports named parameters to callable
+	 * statements.
+	 *
+	 * @return <code>true</code> if named parameters are supported;
+	 *		   <code>false</code> otherwise
+	 */
+	@Override
+	public boolean supportsNamedParameters() {
+		return false;
+	}
+
+	/**
+	 * Retrieves whether it is possible to have multiple <code>ResultSet</code> objects
+	 * returned from a <code>CallableStatement</code> object
+	 * simultaneously.
+	 *
+	 * @return <code>true</code> if a <code>CallableStatement</code> object
+	 *		   can return multiple <code>ResultSet</code> objects
+	 *		   simultaneously; <code>false</code> otherwise
+	 */
+	@Override
+	public boolean supportsMultipleOpenResults() {
+		return true;
+	}
+
+	/**
+	 * Retrieves whether auto-generated keys can be retrieved after
+	 * a statement has been executed.
+	 *
+	 * @return <code>true</code> if auto-generated keys can be retrieved
+	 *		   after a statement has executed; <code>false</code> otherwise
+	 */
+	@Override
+	public boolean supportsGetGeneratedKeys() {
+		return true;
+	}
+
+	/**
+	 * Retrieves a description of the user-defined type (UDT)
+	 * hierarchies defined in a particular schema in this database. Only
+	 * the immediate super type/ sub type relationship is modeled.
+	 * <P>
+	 * Only supertype information for UDTs matching the catalog,
+	 * schema, and type name is returned. The type name parameter
+	 * may be a fully-qualified name. When the UDT name supplied is a
+	 * fully-qualified name, the catalog and schemaPattern parameters are
+	 * ignored.
+	 * <P>
+	 * If a UDT does not have a direct super type, it is not listed here.
+	 * A row of the <code>ResultSet</code> object returned by this method
+	 * describes the designated UDT and a direct supertype. A row has the following
+	 * columns:
+	 *	<OL>
+	 *	<LI><B>TYPE_CAT</B> String => the UDT's catalog (may be <code>null</code>)
+	 *	<LI><B>TYPE_SCHEM</B> String => UDT's schema (may be <code>null</code>)
+	 *	<LI><B>TYPE_NAME</B> String => type name of the UDT
+	 *	<LI><B>SUPERTYPE_CAT</B> String => the direct super type's catalog
+	 *							 (may be <code>null</code>)
+	 *	<LI><B>SUPERTYPE_SCHEM</B> String => the direct super type's schema
+	 *							   (may be <code>null</code>)
+	 *	<LI><B>SUPERTYPE_NAME</B> String => the direct super type's name
+	 *	</OL>
+	 *
+	 * <P><B>Note:</B> If the driver does not support type hierarchies, an
+	 * empty result set is returned.
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog;
+	 *		  <code>null</code> means drop catalog name from the selection criteria
+	 * @param schemaPattern a schema name pattern; "" retrieves those
+	 *		  without a schema
+	 * @param typeNamePattern a UDT name pattern; may be a fully-qualified
+	 *		  name
+	 * @return a <code>ResultSet</code> object in which a row gives information
+	 *		   about the designated UDT
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getSuperTypes(
+		String catalog,
+		String schemaPattern,
+		String typeNamePattern
+	) throws SQLException
+	{
+		String query =
+		"SELECT cast(null as char(1)) AS \"TYPE_CAT\", '' AS \"TYPE_SCHEM\", '' AS \"TYPE_NAME\", " +
+			"cast(null as char(1)) AS \"SUPERTYPE_CAT\", '' AS \"SUPERTYPE_SCHEM\", '' AS \"SUPERTYPE_NAME\" " +
+		"WHERE 1 = 0";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Retrieves a description of the table hierarchies defined in a particular
+	 * schema in this database.
+	 *
+	 * <P>Only supertable information for tables matching the catalog, schema
+	 * and table name are returned. The table name parameter may be a fully-
+	 * qualified name, in which case, the catalog and schemaPattern parameters
+	 * are ignored. If a table does not have a super table, it is not listed here.
+	 * Supertables have to be defined in the same catalog and schema as the
+	 * sub tables. Therefore, the type description does not need to include
+	 * this information for the supertable.
+	 *
+	 * <P>Each type description has the following columns:
+	 *	<OL>
+	 *	<LI><B>TABLE_CAT</B> String => the type's catalog (may be <code>null</code>)
+	 *	<LI><B>TABLE_SCHEM</B> String => type's schema (may be <code>null</code>)
+	 *	<LI><B>TABLE_NAME</B> String => type name
+	 *	<LI><B>SUPERTABLE_NAME</B> String => the direct super type's name
+	 *	</OL>
+	 *
+	 * <P><B>Note:</B> If the driver does not support type hierarchies, an
+	 * empty result set is returned.
+	 *
+	 * @param catalog a catalog name; "" retrieves those without a catalog;
+	 *		  <code>null</code> means drop catalog name from the selection criteria
+	 * @param schemaPattern a schema name pattern; "" retrieves those
+	 *		  without a schema
+	 * @param tableNamePattern a table name pattern; may be a fully-qualified
+	 *		  name
+	 * @return a <code>ResultSet</code> object in which each row is a type description
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getSuperTables(
+		String catalog,
+		String schemaPattern,
+		String tableNamePattern
+	) throws SQLException
+	{
+		String query =
+		"SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
+			"'' AS \"TABLE_SCHEM\", '' AS \"TABLE_NAME\", '' AS \"SUPERTABLE_NAME\" " +
+		"WHERE 1 = 0";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Retrieves a description of the given attribute of the given type
+	 * for a user-defined type (UDT) that is available in the given schema
+	 * and catalog.
+	 * <P>
+	 * Descriptions are returned only for attributes of UDTs matching the
+	 * catalog, schema, type, and attribute name criteria. They are ordered by
+	 * TYPE_SCHEM, TYPE_NAME and ORDINAL_POSITION. This description
+	 * does not contain inherited attributes.
+	 * <P>
+	 * The <code>ResultSet</code> object that is returned has the following
+	 * columns:
+	 * <OL>
+	 *	<LI><B>TYPE_CAT</B> String => type catalog (may be <code>null</code>)
+	 *	<LI><B>TYPE_SCHEM</B> String => type schema (may be <code>null</code>)
+	 *	<LI><B>TYPE_NAME</B> String => type name
+	 *	<LI><B>ATTR_NAME</B> String => attribute name
+	 *	<LI><B>DATA_TYPE</B> int => attribute type SQL type from java.sql.Types
+	 *	<LI><B>ATTR_TYPE_NAME</B> String => Data source dependent type name.
+	 *	For a UDT, the type name is fully qualified. For a REF, the type name is
+	 *	fully qualified and represents the target type of the reference type.
+	 *	<LI><B>ATTR_SIZE</B> int => column size.  For char or date
+	 *		types this is the maximum number of characters; for numeric or
+	 *		decimal types this is precision.
+	 *	<LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits
+	 *	<LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2)
+	 *	<LI><B>NULLABLE</B> int => whether NULL is allowed
+	 *		<UL>
+	 *		<LI> attributeNoNulls - might not allow NULL values
+	 *		<LI> attributeNullable - definitely allows NULL values
+	 *		<LI> attributeNullableUnknown - nullability unknown
+	 *		</UL>
+	 *	<LI><B>REMARKS</B> String => comment describing column (may be <code>null</code>)
+	 *	<LI><B>ATTR_DEF</B> String => default value (may be <code>null</code>)
+	 *	<LI><B>SQL_DATA_TYPE</B> int => unused
+	 *	<LI><B>SQL_DATETIME_SUB</B> int => unused
+	 *	<LI><B>CHAR_OCTET_LENGTH</B> int => for char types the
+	 *		 maximum number of bytes in the column
+	 *	<LI><B>ORDINAL_POSITION</B> int => index of column in table
+	 *		(starting at 1)
+	 *	<LI><B>IS_NULLABLE</B> String => "NO" means column definitely
+	 *		does not allow NULL values; "YES" means the column might
+	 *		allow NULL values.	An empty string means unknown.
+	 *	<LI><B>SCOPE_CATALOG</B> String => catalog of table that is the
+	 *		scope of a reference attribute (<code>null</code> if DATA_TYPE isn't REF)
+	 *	<LI><B>SCOPE_SCHEMA</B> String => schema of table that is the
+	 *		scope of a reference attribute (<code>null</code> if DATA_TYPE isn't REF)
+	 *	<LI><B>SCOPE_TABLE</B> String => table name that is the scope of a
+	 *		reference attribute (<code>null</code> if the DATA_TYPE isn't REF)
+	 * 	<LI><B>SOURCE_DATA_TYPE</B> short => source type of a distinct type or user-generated
+	 *		Ref type,SQL type from java.sql.Types (<code>null</code> if DATA_TYPE
+	 *		isn't DISTINCT or user-generated REF)
+	 *	</OL>
+	 * @param catalog a catalog name; must match the catalog name as it
+	 *		  is stored in the database; "" retrieves those without a catalog;
+	 *		  <code>null</code> means that the catalog name should not be used to narrow
+	 *		  the search
+	 * @param schemaPattern a schema name pattern; must match the schema name
+	 *		  as it is stored in the database; "" retrieves those without a schema;
+	 *		  <code>null</code> means that the schema name should not be used to narrow
+	 *		  the search
+	 * @param typeNamePattern a type name pattern; must match the
+	 *		  type name as it is stored in the database
+	 * @param attributeNamePattern an attribute name pattern; must match the attribute
+	 *		  name as it is declared in the database
+	 * @return a <code>ResultSet</code> object in which each row is an
+	 *		   attribute description
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getAttributes(
+		String catalog,
+		String schemaPattern,
+		String typeNamePattern,
+		String attributeNamePattern
+	) throws SQLException
+	{
+		String query =
+		"SELECT cast(null as char(1)) AS \"TYPE_CAT\", '' AS \"TYPE_SCHEM\", '' AS \"TYPE_NAME\", " +
+			"'' AS \"ATTR_NAME\", CAST(0 as int) AS \"DATA_TYPE\", '' AS \"ATTR_TYPE_NAME\", CAST(0 as int) AS \"ATTR_SIZE\", " +
+			"CAST(0 as int) AS \"DECIMAL_DIGITS\", CAST(0 as int) AS \"NUM_PREC_RADIX\", CAST(0 as int) AS \"NULLABLE\", " +
+			"'' AS \"REMARKS\", '' AS \"ATTR_DEF\", CAST(0 as int) AS \"SQL_DATA_TYPE\", " +
+			"CAST(0 as int) AS \"SQL_DATETIME_SUB\", CAST(0 as int) AS \"CHAR_OCTET_LENGTH\", " +
+			"CAST(0 as int) AS \"ORDINAL_POSITION\", 'YES' AS \"IS_NULLABLE\", " +
+			"'' AS \"SCOPE_CATALOG\", '' AS \"SCOPE_SCHEMA\", '' AS \"SCOPE_TABLE\", " +
+			"CAST(0 as smallint) AS \"SOURCE_DATA_TYPE\" " +
+		"WHERE 1 = 0";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Retrieves whether this database supports the given result set holdability.
+	 *
+	 * @param holdability one of the following constants:
+	 *			<code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
+	 *			<code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
+	 * @return <code>true</code> if so; <code>false</code> otherwise
+	 * @see Connection
+	 */
+	@Override
+	public boolean supportsResultSetHoldability(int holdability) {
+		// we don't close ResultSets at commit; and we don't do updateable
+		// result sets, so comes closest to hold cursors over commit
+		return holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT;
+	}
+
+	/**
+	 * Retrieves the default holdability of this <code>ResultSet</code>
+	 * object.
+	 *
+	 * @return the default holdability; either
+	 *		   <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
+	 *		   <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
+	 */
+	@Override
+	public int getResultSetHoldability() {
+		return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+	}
+
+	/**
+	 * Retrieves the major version number of the underlying database.
+	 *
+	 * @return the underlying database's major version
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public int getDatabaseMajorVersion() throws SQLException {
+		if (env_monet_version == null)
+			getEnvValues();
+		int major = 0;
+		if (env_monet_version != null) {
+			try {
+				int start = env_monet_version.indexOf(".");
+				major = Integer.parseInt(env_monet_version.substring(0, start));
+			} catch (NumberFormatException e) {
+				// ignore
+			}
+		}
+ 		return major;
+	}
+
+	/**
+	 * Retrieves the minor version number of the underlying database.
+	 *
+	 * @return underlying database's minor version
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public int getDatabaseMinorVersion() throws SQLException {
+		if (env_monet_version == null)
+			getEnvValues();
+		int minor = 0;
+		if (env_monet_version != null) {
+			try {
+				int start = env_monet_version.indexOf(".");
+				int end = env_monet_version.indexOf(".", start + 1);
+				minor = Integer.parseInt(env_monet_version.substring(start + 1, end));
+			} catch (NumberFormatException e) {
+				// ignore
+			}
+		}
+ 		return minor;
+	}
+
+	/**
+	 * Retrieves the major JDBC version number for this driver.
+	 *
+	 * @return JDBC version major number
+	 */
+	@Override
+	public int getJDBCMajorVersion() {
+		return 4; // This class implements JDBC 4.1 (at least we try to)
+	}
+
+	/**
+	 * Retrieves the minor JDBC version number for this driver.
+	 *
+	 * @return JDBC version minor number
+	 */
+	@Override
+	public int getJDBCMinorVersion() {
+		return 1; // This class implements JDBC 4.1 (at least we try to)
+	}
+
+	/**
+	 * Indicates whether the SQLSTATEs returned by <code>SQLException.getSQLState</code>
+	 * is X/Open (now known as Open Group) SQL CLI or SQL:2003.
+	 * @return the type of SQLSTATEs, one of:
+	 *		  sqlStateXOpen or
+	 *		  sqlStateSQL
+	 */
+	@Override
+	public int getSQLStateType() {
+		// At least this driver conforms with SQLSTATE to the SQL:2003 standard
+		return DatabaseMetaData.sqlStateSQL;
+	}
+
+	/**
+	 * Indicates whether updates made to a LOB are made on a copy or directly
+	 * to the LOB.
+	 * @return <code>true</code> if updates are made to a copy of the LOB;
+	 *		   <code>false</code> if updates are made directly to the LOB
+	 */
+	@Override
+	public boolean locatorsUpdateCopy() {
+		// not that we have it, but in a transaction it will be copy-on-write
+		return true;
+	}
+
+	/**
+	 * Retrieves whether this database supports statement pooling.
+	 *
+	 * @return <code>true</code> is so;
+		<code>false</code> otherwise
+	 */
+	@Override
+	public boolean supportsStatementPooling() {
+		// For the moment, I don't think so
+		return false;
+	}
+
+	//== 1.6 methods (JDBC 4)
+
+	/**
+	 * Indicates whether or not this data source supports the SQL ROWID
+	 * type, and if so the lifetime for which a RowId object remains
+	 * valid.
+	 *
+	 * @return ROWID_UNSUPPORTED for now
+	 */
+	@Override
+	public RowIdLifetime getRowIdLifetime() {
+		// I believe we don't do rowids
+		return RowIdLifetime.ROWID_UNSUPPORTED;
+	}
+
+	/**
+	 * Get the schema names available in this database.  The results
+	 * are ordered by schema name.
+	 *
+	 * <P>The schema column is:
+	 *	<OL>
+	 *	<LI><B>TABLE_SCHEM</B> String => schema name
+	 *	<LI><B>TABLE_CATALOG</B> String => catalog name (may be null)
+	 *	</OL>
+	 *
+	 * @return ResultSet each row has a single String column that is a
+	 *         schema name
+	 * @throws SQLException if a database error occurs
+	 */
+	@Override
+	public ResultSet getSchemas() throws SQLException {
+		return getSchemas(null, null);
+	}
+
+	/**
+	 * Retrieves whether this database supports invoking user-defined or
+	 * vendor functions using the stored procedure escape syntax.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean supportsStoredFunctionsUsingCallSyntax() {
+		return false;
+	}
+
+	/**
+	 * Retrieves whether a SQLException while autoCommit is true
+	 * inidcates that all open ResultSets are closed, even ones that are
+	 * holdable. When a SQLException occurs while autocommit is true, it
+	 * is vendor specific whether the JDBC driver responds with a commit
+	 * operation, a rollback operation, or by doing neither a commit nor
+	 * a rollback. A potential result of this difference is in whether
+	 * or not holdable ResultSets are closed.
+	 *
+	 * @return true if so; false otherwise
+	 */
+	@Override
+	public boolean autoCommitFailureClosesAllResultSets() {
+		// The driver caches most of it, and as far as I knoww the
+		// server doesn't close outstanding result handles on commit
+		// failure either.
+		return false;
+	}
+
+	/**
+	 * Retrieves a list of the client info properties that the driver
+	 * supports. The result set contains the following columns
+	 *
+	 *    1. NAME String=> The name of the client info property
+	 *    2. MAX_LEN int=> The maximum length of the value for the
+	 *       property
+	 *    3. DEFAULT_VALUE String=> The default value of the
+	 *       property
+	 *    4. DESCRIPTION String=> A description of the
+	 *       property. This will typically contain information as
+	 *       to where this property is stored in the database.
+	 *
+	 * The ResultSet is sorted by the NAME column
+	 *
+	 * @return A ResultSet object; each row is a supported client info
+	 *         property, none in case of MonetDB's current JDBC driver
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getClientInfoProperties() throws SQLException {
+		// for a list of connection properties see also MonetConnection.java constructor MonetConnection(Properties props)
+		String query =
+		"SELECT 'host' AS \"NAME\", CAST(1024 as int) AS \"MAX_LEN\", 'localhost' AS \"DEFAULT_VALUE\", 'DSN or IP-address of machine running MonetDB' AS \"DESCRIPTION\" UNION ALL " +
+		"SELECT 'port', 5, '50000', 'communication port number of MonetDB server process' UNION ALL " +
+		"SELECT 'user', 128, '', 'user name to login to MonetDB server' UNION ALL " +
+		"SELECT 'password', 128, '', 'password for user name to login to MonetDB server' UNION ALL " +
+		"SELECT 'language', 16, 'sql', 'language (sql or mal) used to parse commands in MonetDB server' UNION ALL " +
+		"SELECT 'debug', 5, 'false', 'boolean flag true or false' UNION ALL " +
+		"SELECT 'hash', 128, '', 'hash string' UNION ALL " +
+		"SELECT 'treat_blob_as_binary', 5, 'false', 'boolean flag true or false' UNION ALL " +
+		"SELECT 'so_timeout', 10, '0', 'timeout of communication socket. 0 means no timeout is set' " +
+		"ORDER BY \"NAME\"";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Retrieves a description of the system and user functions
+	 * available in the given catalog.
+	 *
+	 * Only system and user function descriptions matching the schema
+	 * and function name criteria are returned. They are ordered by
+	 * FUNCTION_CAT, FUNCTION_SCHEM, FUNCTION_NAME and SPECIFIC_ NAME.
+	 *
+	 * Each function description has the the following columns:
+	 *
+	 *    1. FUNCTION_CAT String => function catalog (may be null)
+	 *    2. FUNCTION_SCHEM String => function schema (may be null)
+	 *    3. FUNCTION_NAME String => function name. This is the
+	 *       name used to invoke the function
+	 *    4. REMARKS String => explanatory comment on the function
+	 *    5. FUNCTION_TYPE short => kind of function:
+	 *        * functionResultUnknown - Cannot determine if a return
+	 *          value or table will be returned
+	 *        * functionNoTable- Does not return a table
+	 *        * functionReturnsTable - Returns a table
+	 *    6. SPECIFIC_NAME String => the name which uniquely identifies
+	 *       this function within its schema. This is a user specified,
+	 *       or DBMS generated, name that may be different then the
+	 *       FUNCTION_NAME for example with overload functions
+	 *
+	 * A user may not have permission to execute any of the functions
+	 * that are returned by getFunctions.
+	 *
+	 * @param catalog a catalog name; must match the catalog name as it
+	 *        is stored in the database; "" retrieves those without a
+	 *        catalog; null means that the catalog name should not be
+	 *        used to narrow the search
+	 * @param schemaPattern a schema name pattern; must match the schema
+	 *        name as it is stored in the database; "" retrieves those
+	 *        without a schema; null means that the schema name should
+	 *        not be used to narrow the search
+	 * @param functionNamePattern a function name pattern; must match
+	 *        the function name as it is stored in the database
+	 * @return ResultSet - each row is a function description
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getFunctions(
+			String catalog,
+			String schemaPattern,
+			String functionNamePattern)
+		throws SQLException
+	{
+		StringBuilder query = new StringBuilder(800);
+		query.append("SELECT cast(null as varchar(1)) AS \"FUNCTION_CAT\", " +
+			"\"schemas\".\"name\" AS \"FUNCTION_SCHEM\", " +
+			"\"functions\".\"name\" AS \"FUNCTION_NAME\", " +
+			"cast(\"functions\".\"func\" as varchar(9999)) AS \"REMARKS\", " +
+			"CASE \"functions\".\"type\"" +
+				" WHEN 1 THEN ").append(DatabaseMetaData.functionNoTable)
+			.append(" WHEN 2 THEN ").append(DatabaseMetaData.functionNoTable)
+			.append(" WHEN 3 THEN ").append(DatabaseMetaData.functionNoTable)
+			.append(" WHEN 4 THEN ").append(DatabaseMetaData.functionNoTable)
+			.append(" WHEN 5 THEN ").append(DatabaseMetaData.functionReturnsTable)
+			.append(" ELSE ").append(DatabaseMetaData.functionResultUnknown).append(" END AS \"FUNCTION_TYPE\", " +
+			// only the id value uniquely identifies a function. Include it to be able to differentiate between multiple overloaded functions with the same name
+			"cast(\"functions\".\"id\" as varchar(10)) AS \"SPECIFIC_NAME\" " +
+		"FROM \"sys\".\"functions\", \"sys\".\"schemas\" " +
+		"WHERE \"functions\".\"schema_id\" = \"schemas\".\"id\" " +
+		// exclude procedures (type = 2). Those need to be returned via getProcedures()
+		"AND \"functions\".\"type\" <> 2");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (functionNamePattern != null) {
+			query.append(" AND \"functions\".\"name\" ").append(composeMatchPart(functionNamePattern));
+		}
+
+		query.append(" ORDER BY \"FUNCTION_SCHEM\", \"FUNCTION_NAME\", \"SPECIFIC_NAME\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	/**
+	 * Retrieves a description of the given catalog's system or user
+	 * function parameters and return type.
+	 *
+	 * Only descriptions matching the schema, function and parameter name criteria are returned.
+	 * They are ordered by FUNCTION_CAT, FUNCTION_SCHEM, FUNCTION_NAME and SPECIFIC_ NAME.
+	 * Within this, the return value, if any, is first. Next are the parameter descriptions in call order.
+	 * The column descriptions follow in column number order.
+	 *
+	 * 1.  FUNCTION_CAT String => function catalog (may be null)
+	 * 2.  FUNCTION_SCHEM String => function schema (may be null)
+	 * 3.  FUNCTION_NAME String => function name. This is the name used to invoke the function
+	 * 4.   COLUMN_NAME String => column/parameter name
+	 * 5.   COLUMN_TYPE Short => kind of column/parameter:
+	 *         functionColumnUnknown - nobody knows
+	 *         functionColumnIn - IN parameter
+	 *         functionColumnInOut - INOUT parameter
+	 *         functionColumnOut - OUT parameter
+	 *         functionColumnReturn - function return value
+	 *         functionColumnResult - Indicates that the parameter or column is a column in the ResultSet
+	 * 6.   DATA_TYPE int => SQL type from java.sql.Types
+	 * 7.   TYPE_NAME String => SQL type name, for a UDT type the type name is fully qualified
+	 * 8.   PRECISION int => precision
+	 * 9.   LENGTH int => length in bytes of data
+	 * 10.  SCALE short => scale - null is returned for data types where SCALE is not applicable.
+	 * 11.  RADIX short => radix
+	 * 12.  NULLABLE short => can it contain NULL.
+	 *         functionNoNulls - does not allow NULL values
+	 *         functionNullable - allows NULL values
+	 *         functionNullableUnknown - nullability unknown
+	 * 13.  REMARKS String => comment describing column/parameter
+	 * 14.  CHAR_OCTET_LENGTH int => the maximum length of binary and character based parameters or columns. For any other datatype the returned value is a NULL
+	 * 15.  ORDINAL_POSITION int => the ordinal position, starting from 1, for the input and output parameters.
+	 * 	   A value of 0 is returned if this row describes the function's return value. For result set columns, it is the ordinal position of the column in the result set starting from 1.
+	 * 16.  IS_NULLABLE String => ISO rules are used to determine the nullability for a parameter or column.
+	 *         YES --- if the parameter or column can include NULLs
+	 *         NO --- if the parameter or column cannot include NULLs
+	 *         empty string --- if the nullability for the parameter or column is unknown
+	 * 17.  SPECIFIC_NAME String => the name which uniquely identifies this function within its schema.
+	 * 	  This is a user specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload functions
+	 *
+	 * @param catalog a catalog name; must match the catalog name as
+	 *        it is stored in the database; "" retrieves those without a
+	 *        catalog; null means that the catalog name should not be
+	 *        used to narrow the search
+	 * @param schemaPattern a schema name pattern; must match the schema
+	 *        name as it is stored in the database; "" retrieves those
+	 *        without a schema; null means that the schema name should
+	 *        not be used to narrow the search
+	 * @param functionNamePattern a procedure name pattern; must match the
+	 *        function name as it is stored in the database
+	 * @param columnNamePattern a parameter name pattern; must match the
+	 *        parameter or column name as it is stored in the database
+	 * @return ResultSet - each row describes a user function parameter,
+	 *         column or return type
+	 * @throws SQLException - if a database access error occurs
+	 */
+	@Override
+	public ResultSet getFunctionColumns(
+			String catalog,
+			String schemaPattern,
+			String functionNamePattern,
+			String columnNamePattern)
+		throws SQLException
+	{
+		StringBuilder query = new StringBuilder(2600);
+		query.append("SELECT DISTINCT CAST(null as char(1)) AS \"FUNCTION_CAT\", " +
+			"\"schemas\".\"name\" AS \"FUNCTION_SCHEM\", " +
+			"\"functions\".\"name\" AS \"FUNCTION_NAME\", " +
+			"\"args\".\"name\" AS \"COLUMN_NAME\", " +
+			"CAST(CASE \"args\".\"inout\"" +
+				" WHEN 0 THEN (CASE \"args\".\"number\" WHEN 0 THEN ")
+				.append(DatabaseMetaData.functionReturn).append(" ELSE ").append(DatabaseMetaData.functionColumnOut).append(" END)" +
+				" WHEN 1 THEN ").append(DatabaseMetaData.functionColumnIn)
+				.append(" ELSE ").append(DatabaseMetaData.functionColumnUnknown).append(" END AS smallint) AS \"COLUMN_TYPE\", " +
+			"CAST(").append(MonetDriver.getSQLTypeMap("\"args\".\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
+			"\"args\".\"type\" AS \"TYPE_NAME\", " +
+			"CASE \"args\".\"type\" WHEN 'tinyint' THEN 3 WHEN 'smallint' THEN 5 WHEN 'int' THEN 10 WHEN 'bigint' THEN 19" +
+				" WHEN 'hugeint' THEN 38 WHEN 'oid' THEN 19 WHEN 'wrd' THEN 19 ELSE \"args\".\"type_digits\" END AS \"PRECISION\", " +
+			"CASE \"args\".\"type\" WHEN 'tinyint' THEN 1 WHEN 'smallint' THEN 2 WHEN 'int' THEN 4 WHEN 'bigint' THEN 8" +
+				" WHEN 'hugeint' THEN 16 WHEN 'oid' THEN 8 WHEN 'wrd' THEN 8 ELSE \"args\".\"type_digits\" END AS \"LENGTH\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('tinyint','smallint','int','bigint','hugeint','oid','wrd','decimal','numeric'," +
+				"'time','timetz','timestamp','timestamptz','sec_interval') THEN \"args\".\"type_scale\" ELSE NULL END AS smallint) AS \"SCALE\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('tinyint','smallint','int','bigint','hugeint','oid','wrd','decimal','numeric') THEN 10" +
+				" WHEN \"args\".\"type\" IN ('real','float','double') THEN 2 ELSE NULL END AS smallint) AS \"RADIX\", " +
+			"CAST(").append(DatabaseMetaData.functionNullableUnknown).append(" AS smallint) AS \"NULLABLE\", " +
+			"CAST(null as char(1)) AS \"REMARKS\", " +
+			"CAST(CASE WHEN \"args\".\"type\" IN ('char','varchar','clob') THEN \"args\".\"type_digits\" ELSE NULL END as int) AS \"CHAR_OCTET_LENGTH\", " +
+			"CAST(\"args\".\"number\" as int) AS \"ORDINAL_POSITION\", " +
+			"CAST('' as varchar(3)) AS \"IS_NULLABLE\", " +
+			// the specific name contains the function id, in order to be able to match the args to the correct overloaded function name
+			"cast(\"functions\".\"id\" as varchar(10)) AS \"SPECIFIC_NAME\" " +
+		"FROM \"sys\".\"args\", \"sys\".\"functions\", \"sys\".\"schemas\" " +
+		"WHERE \"args\".\"func_id\" = \"functions\".\"id\" " +
+		"AND \"functions\".\"schema_id\" = \"schemas\".\"id\" " +
+		// exclude procedures (type = 2). Those need to be returned via getProcedureColumns()
+		"AND \"functions\".\"type\" <> 2");
+
+		if (catalog != null && catalog.length() > 0) {
+			// none empty catalog selection.
+			// as we do not support catalogs this always results in no rows returned
+			query.append(" AND 1 = 0");
+		}
+		if (schemaPattern != null) {
+			query.append(" AND \"schemas\".\"name\" ").append(composeMatchPart(schemaPattern));
+		}
+		if (functionNamePattern != null) {
+			query.append(" AND \"functions\".\"name\" ").append(composeMatchPart(functionNamePattern));
+		}
+		if (columnNamePattern != null) {
+			query.append(" AND \"args\".\"name\" ").append(composeMatchPart(columnNamePattern));
+		}
+		query.append(" ORDER BY \"FUNCTION_SCHEM\", \"FUNCTION_NAME\", \"SPECIFIC_NAME\", \"ORDINAL_POSITION\"");
+
+		return executeMetaDataQuery(query.toString());
+	}
+
+	//== 1.7 methods (JDBC 4.1)
+	
+	/**
+	 * Retrieves a description of the pseudo or hidden columns available
+	 * in a given table within the specified catalog and schema.  Pseudo
+	 * or hidden columns may not always be stored within a table and are
+	 * not visible in a ResultSet unless they are specified in the
+	 * query's outermost SELECT list.  Pseudo or hidden columns may not
+	 * necessarily be able to be modified.
+	 * If there are no pseudo or hidden columns, an empty ResultSet is returned.
+	 *
+	 * Only column descriptions matching the catalog, schema, table and column name criteria are returned.
+	 * They are ordered by TABLE_CAT,TABLE_SCHEM, TABLE_NAME and COLUMN_NAME.
+	 *
+	 * Each column description has the following columns:
+	 *
+	 *  1. TABLE_CAT String => table catalog (may be null)
+	 *  2. TABLE_SCHEM String => table schema (may be null)
+	 *  3. TABLE_NAME String => table name
+	 *  4. COLUMN_NAME String => column name
+	 *  5. DATA_TYPE int => SQL type from java.sql.Types
+	 *  6. COLUMN_SIZE int => column size.
+	 *  7. DECIMAL_DIGITS int => the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable.
+	 *  8. NUM_PREC_RADIX int => Radix (typically either 10 or 2)
+	 *  9. COLUMN_USAGE String => The allowed usage for the column. The value returned will correspond to the enum name returned by PseudoColumnUsage.name()
+	 * 10. REMARKS String => comment describing column (may be null)
+	 * 11. CHAR_OCTET_LENGTH int => for char types the maximum number of bytes in the column
+	 * 12. IS_NULLABLE String => ISO rules are used to determine the nullability for a column.
+	 *         YES --- if the column can include NULLs
+	 *         NO --- if the column cannot include NULLs
+	 *         empty string --- if the nullability for the column is unknown
+	 *
+	 * @param catalog a catalog name
+	 * @param schemaPattern a schema name pattern
+	 * @param tableNamePattern a table name pattern
+	 * @param columnNamePattern a column name pattern
+	 * @return ResultSet where each row is a column description
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getPseudoColumns(
+			String catalog,
+			String schemaPattern,
+			String tableNamePattern,
+			String columnNamePattern)
+		throws SQLException
+	{
+		// MonetDB currently does not support pseudo or hidden columns, so return an empty ResultSet
+		String query =
+		"SELECT CAST(null as char(1)) AS \"TABLE_CAT\", " +
+			"CAST('' as varchar(1)) AS \"TABLE_SCHEM\", " +
+			"CAST('' as varchar(1)) AS \"TABLE_NAME\", " +
+			"CAST('' as varchar(1)) AS \"COLUMN_NAME\", " +
+			"CAST(0 as int) AS \"DATA_TYPE\", " +
+			"CAST(0 as int) AS \"COLUMN_SIZE\", " +
+			"CAST(0 as int) AS \"DECIMAL_DIGITS\", " +
+			"CAST(0 as int) AS \"NUM_PREC_RADIX\", " +
+			"CAST('' as varchar(1)) AS \"COLUMN_USAGE\", " +
+			"CAST(null as varchar(1)) AS \"REMARKS\", " +
+			"CAST(0 as int) AS \"CHAR_OCTET_LENGTH\", " +
+			"CAST('' as varchar(3)) AS \"IS_NULLABLE\" " +
+		"WHERE 1 = 0";
+
+		return executeMetaDataQuery(query);
+	}
+
+	/**
+	 * Retrieves whether a generated key will always be returned if the
+	 * column name(s) or index(es) specified for the auto generated key
+	 * column(s) are valid and the statement succeeds.  The key that is
+	 * returned may or may not be based on the column(s) for the auto
+	 * generated key.
+	 *
+	 * @return true if so, false otherwise
+	 * @throws SQLException - if a database access error occurs
+	 */
+	@Override
+	public boolean generatedKeyAlwaysReturned() throws SQLException {
+		return true;
+	}
+
+	//== end methods interface DatabaseMetaData
+}