view 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 source
/*
 * 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
}