changeset 653:5eb9d54057e6

Improved DatabaseMetaData.getBestRowIdentifier() further by introducing an extra cte: tableids. It makes it easier to understand/maintain and possibly faster. Also implemented a TODO: when there is No PK and No unique constraints. It now returns all columns of the table, but none for views. Also updated and extended JDBC_API_Tester program.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 25 Aug 2022 15:46:06 +0200 (2022-08-25)
parents 6a34d2c36dec
children 755c05380872
files src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java tests/JDBC_API_Tester.java
diffstat 2 files changed, 464 insertions(+), 113 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -2420,11 +2420,11 @@ public class MonetDatabaseMetaData
 		 * Instead of the first (in case of multiple) we should potentially use the uc which has
 		 *  a) the least number of columns and
 		 *  b) the smallest total(size in bytes).
-		 * That's complex to built in SQL.
+		 * That's much more complex to do in SQL than the current implementation, which is fast and gives a correct result.
 		 */
-		// TODO: when there is No PK and No unique constraints, we potentially should return all columns of the table (else in SQuirreL no header is shown in the "Row IDs" tab)
-
-		final StringBuilder query = new StringBuilder(2600);
+
+		final StringBuilder query = new StringBuilder(3000);
+		// 1st cte: syskeys
 		query.append("with syskeys as (" +
 			// all pkeys
 			"SELECT \"id\", \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" = 0 " +
@@ -2435,6 +2435,7 @@ public class MonetDatabaseMetaData
 			"AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"sys\".\"keys\" where \"type\" = 1 group by \"table_id\"))");
 		if (incltmpkey) {
 			// we must also include the primary key or unique constraint of local temporary tables which are stored in tmp.keys
+		// 2nd cte: tmpkeys
 			query.append(", tmpkeys as (" +
 			"SELECT \"id\", \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" = 0 " +
 			"UNION ALL " +
@@ -2442,72 +2443,76 @@ public class MonetDatabaseMetaData
 			"AND \"table_id\" NOT IN (select \"table_id\" from \"tmp\".\"keys\" where \"type\" = 0) " +
 			"AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"tmp\".\"keys\" where \"type\" = 1 group by \"table_id\"))");
 		}
-		query.append(", cols as (" +
-			"SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " +
-			"FROM syskeys k " +
-			"JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " +
-			"JOIN \"sys\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
-			"JOIN \"sys\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
+		// 3rd cte: tableids
+		query.append(", tableids as (" +
+			"SELECT t.\"id\" " +
+			"FROM \"sys\".\"tables\" t " +
 			"JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
-			"WHERE 1=1");
+			"WHERE t.\"type\" NOT IN (1, 11) ");	// exclude all VIEWs and SYSTEM VIEWs
 		if (catalog != null && !catalog.isEmpty()) {
 			// non-empty catalog selection.
 			// as we do not support catalogs this always results in no rows returned
-			query.append(" AND 1=0");
+			query.append("AND 1=0");
 		} else {
 			if (scope == DatabaseMetaData.bestRowSession
 			 || scope == DatabaseMetaData.bestRowTransaction
 			 || scope == DatabaseMetaData.bestRowTemporary) {
 				if (schema != null) {
 					// do not allow wildcard matching with LIKE, as the resultset does not include the schema info
-					query.append(" AND s.\"name\" = ").append(MonetWrapper.sq(schema));
+					query.append("AND s.\"name\" = ").append(MonetWrapper.sq(schema));
 				}
 				if (table != null) {
 					// do not allow wildcard matching with LIKE, as the resultset does not include the table info
 					query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table));
 				}
-				if (!nullable) {
-					query.append(" AND c.\"null\" = false");
-				}
 			} else {
-				query.append(" AND 1=0");
+				query.append("AND 1=0");
 			}
 		}
+		// 4th cte: cols, this unions 2 (or 4 when incltmpkey == true) select queries
+		query.append("), cols as (" +
+			"SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " +
+			"FROM syskeys k " +
+			"JOIN tableids t ON k.\"table_id\" = t.\"id\" " +
+			"JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " +
+			"JOIN \"sys\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\")");
+		if (!nullable) {
+			query.append(" WHERE c.\"null\" = false");
+		}
 		if (incltmpkey) {
 			// we must also include the primary key or unique constraint of local temporary tables
 			// which are stored in tmp.keys, tmp.objects, tmp._tables and tmp._columns
 			query.append(" UNION ALL " +
 			"SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " +
 			"FROM tmpkeys k " +
+			"JOIN tableids t ON k.\"table_id\" = t.\"id\" " +
 			"JOIN \"tmp\".\"objects\" o ON k.\"id\" = o.\"id\" " +
-			"JOIN \"tmp\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
-			"JOIN \"tmp\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
-			"JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
-			"WHERE 1=1");
-			if (catalog != null && !catalog.isEmpty()) {
-				// non-empty catalog selection.
-				// as we do not support catalogs this always results in no rows returned
-				query.append(" AND 1=0");
-			} else {
-				if (scope == DatabaseMetaData.bestRowSession
-				 || scope == DatabaseMetaData.bestRowTransaction
-				 || scope == DatabaseMetaData.bestRowTemporary) {
-					if (schema != null) {
-						// do not allow wildcard matching with LIKE, as the resultset does not include the schema info
-						query.append(" AND s.\"name\" = ").append(MonetWrapper.sq(schema));
-					}
-					if (table != null) {
-						// do not allow wildcard matching with LIKE, as the resultset does not include the table info
-						query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table));
-					}
-					if (!nullable) {
-						query.append(" AND c.\"null\" = false");
-					}
-				} else {
-					query.append(" AND 1=0");
-				}
+			"JOIN \"tmp\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\")");
+			if (!nullable) {
+				query.append(" WHERE c.\"null\" = false");
 			}
 		}
+		// when there is No PK and No unique constraints, we should return all columns of the table
+		// (else in SQuirreL no header is shown in the "Row IDs" tab)
+		query.append(" UNION ALL " +
+			"SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", c.\"number\" " +
+			"FROM tableids t " +
+			"JOIN \"sys\".\"_columns\" c ON t.\"id\" = c.\"table_id\" " +
+			"WHERE t.\"id\" NOT IN (SELECT \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" in (0, 1))");
+		if (!nullable) {
+			query.append(" AND c.\"null\" = false");
+		}
+		if (incltmpkey) {
+			query.append(" UNION ALL " +
+			"SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", c.\"number\" " +
+			"FROM tableids t " +
+			"JOIN \"tmp\".\"_columns\" c ON t.\"id\" = c.\"table_id\" " +
+			"WHERE t.\"id\" NOT IN (SELECT \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" in (0, 1))");
+			if (!nullable) {
+				query.append(" AND c.\"null\" = false");
+			}
+		}
+		// the final select query
 		query.append(") SELECT " +
 			"cast(").append(DatabaseMetaData.bestRowSession).append(" AS smallint) AS \"SCOPE\", " +
 			"c.\"name\" AS \"COLUMN_NAME\", " +
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -60,6 +60,7 @@ final public class JDBC_API_Tester {
 		jt.Test_Csavepoints();
 		jt.Test_Ctransaction();
 		jt.Test_Dobjects();
+		jt.Test_DBCmetadata();
 		jt.Test_FetchSize();
 		jt.Test_Int128();
 		jt.Test_PlanExplainTraceDebugCmds();
@@ -676,49 +677,44 @@ final public class JDBC_API_Tester {
 			"13. commit...failed as expected: COMMIT: not allowed in auto commit mode\n");
 	}
 
+	private void handleExecuteDDL(Statement stmt, String action, String objtype, String objname, String sql) {
+		try {
+			int response = stmt.executeUpdate(sql);
+			if (response != Statement.SUCCESS_NO_INFO)
+				sb.append(action).append(" ").append(objtype).append(" ").append(objname).append(" failed to return -2!! It returned: ").append(response).append("\n");
+		} catch (SQLException e) {
+			sb.append("Failed to ").append(action).append(" ").append(objtype).append(" ").append(objname).append(": ").append(e.getMessage()).append("\n");
+		}
+	}
+
 	private void Test_Dobjects() {
 		sb.setLength(0);	// clear the output log buffer
 
-		String tablename = "";
-		int response;
 		Statement stmt = null;
 		try {
 			stmt = con.createStatement();
-			tablename = "nopk_twoucs";
-			response = stmt.executeUpdate("CREATE TABLE nopk_twoucs (id INT NOT NULL UNIQUE, name VARCHAR(99) UNIQUE)");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "tmp_nopk_twoucs";
-			response = stmt.executeUpdate("CREATE LOCAL TEMP TABLE tmp_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE)");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "tmp_pk_uc";
-			response = stmt.executeUpdate("CREATE LOCAL TEMP TABLE tmp_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE)");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "glbl_nopk_twoucs";
-			response = stmt.executeUpdate("CREATE GLOBAL TEMP TABLE glbl_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE)");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "glbl_pk_uc";
-			response = stmt.executeUpdate("CREATE GLOBAL TEMP TABLE glbl_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE)");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
 		} catch (SQLException e) {
-			sb.append("failed to create test table ").append(tablename).append(": ").append(e.getMessage()).append("\n");
+			sb.append("failed to createStatement: ").append(e.getMessage()).append("\n");
 		}
 
-		try {
-			response = stmt.executeUpdate("CREATE TYPE xml EXTERNAL NAME xml");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Creating type xml failed to return -2!! It returned: ").append(response).append("\n");
-		} catch (SQLException e) {
-			sb.append("failed to create type xml: ").append(e.getMessage()).append("\n");
-		}
+		String action = "Create";
+		final String objtype = "table";
+		handleExecuteDDL(stmt, action, objtype, "nopk_twoucs",
+			"CREATE TABLE nopk_twoucs (id INT NOT NULL UNIQUE, name VARCHAR(99) UNIQUE)");
+
+		handleExecuteDDL(stmt, action, objtype, "tmp_nopk_twoucs",
+			"CREATE LOCAL TEMP TABLE tmp_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE)");
+
+		handleExecuteDDL(stmt, action, objtype, "tmp_pk_uc",
+			"CREATE LOCAL TEMP TABLE tmp_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE)");
+
+		handleExecuteDDL(stmt, action, objtype, "glbl_nopk_twoucs",
+			"CREATE GLOBAL TEMP TABLE glbl_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE)");
+
+		handleExecuteDDL(stmt, action, objtype, "glbl_pk_uc",
+			"CREATE GLOBAL TEMP TABLE glbl_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE)");
+
+		handleExecuteDDL(stmt, action, "type", "xml", "CREATE TYPE xml EXTERNAL NAME xml");
 
 		try {
 			DatabaseMetaData dbmd = con.getMetaData();
@@ -914,42 +910,13 @@ final public class JDBC_API_Tester {
 		}
 
 		// cleanup created db objects
-		try {
-			tablename = "nopk_twoucs";
-			response = stmt.executeUpdate("DROP TABLE " + tablename);
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "tmp_nopk_twoucs";
-			response = stmt.executeUpdate("DROP TABLE " + tablename);
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "tmp_pk_uc";
-			response = stmt.executeUpdate("DROP TABLE " + tablename);
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "glbl_nopk_twoucs";
-			response = stmt.executeUpdate("DROP TABLE " + tablename);
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-
-			tablename = "glbl_pk_uc";
-			response = stmt.executeUpdate("DROP TABLE " + tablename);
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping table ").append(tablename).append(" failed to return -2!! It returned: ").append(response).append("\n");
-		} catch (SQLException e) {
-			sb.append("failed to drop test table ").append(tablename).append(": ").append(e.getMessage()).append("\n");
-		}
-
-		try {
-			response = stmt.executeUpdate("DROP TYPE xml");
-			if (response != Statement.SUCCESS_NO_INFO)
-				sb.append("Dropping type xml failed to return -2!! It returned: ").append(response).append("\n");
-		} catch (SQLException e) {
-			sb.append("failed to drop type: ").append(e.getMessage()).append("\n");
-		}
+		action = "Drop";
+		handleExecuteDDL(stmt, action, objtype, "nopk_twoucs", "DROP TABLE nopk_twoucs");
+		handleExecuteDDL(stmt, action, objtype, "tmp_nopk_twoucs", "DROP TABLE tmp_nopk_twoucs");
+		handleExecuteDDL(stmt, action, objtype, "tmp_pk_uc", "DROP TABLE tmp_pk_uc");
+		handleExecuteDDL(stmt, action, objtype, "glbl_nopk_twoucs", "DROP TABLE glbl_nopk_twoucs");
+		handleExecuteDDL(stmt, action, objtype, "glbl_pk_uc", "DROP TABLE glbl_pk_uc");
+		handleExecuteDDL(stmt, action, "type", "xml", "DROP TYPE xml");
 
 		closeStmtResSet(stmt, null);
 
@@ -1010,6 +977,385 @@ final public class JDBC_API_Tester {
 		compareExpectedOutput(methodnm, expected);
 	}
 
+	// same tests as done in clients/odbc/tests/ODBCmetadata.c
+	private void Test_DBCmetadata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		DatabaseMetaData dbmd = null;
+		try {
+			stmt = con.createStatement();
+			dbmd = con.getMetaData();
+		} catch (SQLException e) {
+			sb.append("Failed to createStatement: ").append(e.getMessage()).append("\n");
+		}
+
+		String action = "Create";
+		handleExecuteDDL(stmt, action, "schema", "jdbctst", "CREATE SCHEMA jdbctst; SET SCHEMA jdbctst;");
+
+		String objtype = "table";
+		// create tables to populate data dictionary. Used for testing getTables(),
+		// getColumns(), getBestRowIdentifier(), getPrimaryKeys(),
+		// getExportedKeys(), getImportedKeys(), getCrossReference()
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc",
+			"CREATE TABLE jdbctst.pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE);");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc",
+			"CREATE LOCAL TEMP TABLE tmp.tmp_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE);");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_pk_uc",
+			"CREATE GLOBAL TEMP TABLE tmp.glbl_pk_uc (id1 INT NOT NULL PRIMARY KEY, name1 VARCHAR(99) UNIQUE);");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs",
+			"CREATE TABLE jdbctst.nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE);");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_nopk_twoucs",
+			"CREATE LOCAL TEMP TABLE tmp.tmp_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE);");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_nopk_twoucs",
+			"CREATE GLOBAL TEMP TABLE tmp.glbl_nopk_twoucs (id2 INT NOT NULL UNIQUE, name2 VARCHAR(99) UNIQUE);");
+		/* next 3 tables copied from example in https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlforeignkeys-function?view=sql-server-ver15 */
+		handleExecuteDDL(stmt, action, objtype, "\"CUSTOMERS\"",
+			"CREATE TABLE \"CUSTOMERS\" (\"CUSTID\" INT PRIMARY KEY, \"NAME\" VARCHAR(60) NOT NULL, \"ADDRESS\" VARCHAR(90), \"PHONE\" VARCHAR(20));");
+		handleExecuteDDL(stmt, action, objtype, "\"ORDERS\"",
+			"CREATE TABLE \"ORDERS\" (\"ORDERID\" INT PRIMARY KEY, \"CUSTID\" INT NOT NULL REFERENCES \"CUSTOMERS\" (\"CUSTID\")" +
+			", \"OPENDATE\" DATE NOT NULL, \"SALESPERSON\" VARCHAR(60), \"STATUS\" VARCHAR(10) NOT NULL);");
+		handleExecuteDDL(stmt, action, objtype, "\"LINES\"",
+			"CREATE TABLE \"LINES\" (\"ORDERID\" INT NOT NULL REFERENCES \"ORDERS\" (\"ORDERID\"), \"LINES\" INT" +
+			", PRIMARY KEY (\"ORDERID\", \"LINES\"), \"PARTID\" INT NOT NULL, \"QUANTITY\" DECIMAL(9,3) NOT NULL);");
+		/* also test situation where one table has multiple fks to the same multi column pk */
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk2c",
+			"CREATE TABLE jdbctst.pk2c (pkc1 INT, pkc2 VARCHAR(99), name1 VARCHAR(99) UNIQUE, PRIMARY KEY (pkc2, pkc1));");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.fk2c",
+			"CREATE TABLE jdbctst.fk2c (fkc1 INT NOT NULL PRIMARY KEY, fkc2 VARCHAR(99), fkc3 INT" +
+			", FOREIGN KEY (fkc2, fkc1) REFERENCES jdbctst.pk2c (pkc2, pkc1) ON UPDATE CASCADE ON DELETE RESTRICT" +
+			", FOREIGN KEY (fkc2, fkc3) REFERENCES jdbctst.pk2c (pkc2, pkc1) ON UPDATE SET NULL ON DELETE NO ACTION);");
+
+		// create indexes to populate catalog. Used for testing getIndexInfo()
+		objtype = "index";
+		handleExecuteDDL(stmt, action, objtype, "pk_uc_i",
+			"CREATE INDEX pk_uc_i ON jdbctst.pk_uc (id1, name1);");
+		handleExecuteDDL(stmt, action, objtype, "tmp_pk_uc_i",
+			"CREATE INDEX tmp_pk_uc_i ON tmp.tmp_pk_uc (id1, name1);");
+		handleExecuteDDL(stmt, action, objtype, "glbl_pk_uc_i",
+			"CREATE INDEX glbl_pk_uc_i ON tmp.glbl_pk_uc (id1, name1);");
+		handleExecuteDDL(stmt, action, objtype, "nopk_twoucs_i",
+			"CREATE INDEX nopk_twoucs_i ON jdbctst.nopk_twoucs (id2, name2);");
+		handleExecuteDDL(stmt, action, objtype, "tmp_nopk_twoucs_i",
+			"CREATE INDEX tmp_nopk_twoucs_i ON tmp.tmp_nopk_twoucs (id2, name2);");
+		handleExecuteDDL(stmt, action, objtype, "glbl_nopk_twoucs_i",
+			"CREATE INDEX glbl_nopk_twoucs_i ON tmp.glbl_nopk_twoucs (id2, name2);");
+
+		// grant privileges to populate catalog. Used for testing getTablePrivileges() and getColumnPrivileges()
+		action = "grant";
+		objtype = "table";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc",
+			"GRANT SELECT ON TABLE jdbctst.pk_uc TO PUBLIC;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc",
+			"GRANT INSERT, UPDATE, DELETE ON TABLE jdbctst.pk_uc TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs",
+			"GRANT SELECT (id2, name2), UPDATE (name2) ON TABLE jdbctst.nopk_twoucs TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc",
+			"GRANT INSERT, DELETE ON TABLE tmp.tmp_pk_uc TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc",
+			"GRANT SELECT (id1, name1), UPDATE (name1) ON TABLE tmp.tmp_pk_uc TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc ",
+			"GRANT INSERT, DELETE ON TABLE tmp.tmp_pk_uc  TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_pk_uc",
+			"GRANT SELECT (id1, name1), UPDATE (name1) ON TABLE tmp.glbl_pk_uc TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_nopk_twoucs",
+			"GRANT INSERT, DELETE ON TABLE tmp.tmp_nopk_twoucs TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_nopk_twoucs",
+			"GRANT SELECT (id2, name2), UPDATE (name2) ON TABLE tmp.tmp_nopk_twoucs TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_nopk_twoucs",
+			"GRANT DELETE, INSERT ON TABLE tmp.glbl_nopk_twoucs TO monetdb;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_nopk_twoucs",
+			"GRANT SELECT (id2, name2), UPDATE (name2) ON TABLE tmp.glbl_nopk_twoucs TO monetdb;");
+
+		// TODO add user procedures / functions to test getProcedures() and getProcedureColumns() more
+
+		// set COMMENT ON schema, tables, columns, indexes, procedures and functions to fetch (and test) data in the REMARKS result column
+		action = "comment on";
+		handleExecuteDDL(stmt, action, "schema", "jdbctst",
+			"COMMENT ON SCHEMA jdbctst IS 'jdbctst schema comment';");
+		objtype = "table";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc",
+			"COMMENT ON TABLE jdbctst.pk_uc IS 'jdbctst.pk_uc table comment';");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs",
+			"COMMENT ON TABLE jdbctst.nopk_twoucs IS 'jdbctst.nopk_twoucs table comment';");
+		objtype = "column";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs.id2",
+			"COMMENT ON COLUMN jdbctst.nopk_twoucs.id2 IS 'jdbctst.nopk_twoucs.id2 column comment';");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs.name2",
+			"COMMENT ON COLUMN jdbctst.nopk_twoucs.name2 IS 'jdbctst.nopk_twoucs.name2 column comment';");
+		objtype = "index";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc_i",
+			"COMMENT ON INDEX jdbctst.pk_uc_i IS 'jdbctst.pk_uc_i index comment';");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs_i",
+			"COMMENT ON INDEX jdbctst.nopk_twoucs_i IS 'jdbctst.nopk_twoucs_i index comment';");
+		objtype = "procedure";
+		handleExecuteDDL(stmt, action, objtype, "sys.analyze()",
+			"COMMENT ON PROCEDURE sys.analyze() IS 'sys.analyze() procedure comment';");
+		objtype = "function";
+		handleExecuteDDL(stmt, action, objtype, "sys.sin(double)",
+			"COMMENT ON FUNCTION sys.sin(double) IS 'sys.sin(double) function comment';");
+		handleExecuteDDL(stmt, action, objtype, "sys.env()",
+			"COMMENT ON FUNCTION sys.env() IS 'sys.env() function comment';");
+		handleExecuteDDL(stmt, action, objtype, "sys.statistics()",
+			"COMMENT ON FUNCTION sys.statistics() IS 'sys.statistics() function comment';");
+
+		try {
+			// query the catalog by calling DatabaseMetaData methods
+			compareResultSet(dbmd.getCatalogs(), "getCatalogs()",
+			"Resultset with 1 columns\n" +
+			"TABLE_CAT\n" +
+			"char(1)\n");
+
+			compareResultSet(dbmd.getSchemas(null, "jdbctst"), "getSchemas(null, jdbctst)",
+			"Resultset with 2 columns\n" +
+			"TABLE_SCHEM	TABLE_CATALOG\n" +
+			"varchar(7)	char(1)\n" +
+			"jdbctst	null\n");
+
+			compareResultSet(dbmd.getTables(null, "jdbctst", null, null), "getTables(null, jdbctst, null, null)",
+			"Resultset with 10 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION\n" +
+			"char(1)	varchar(7)	varchar(11)	varchar(5)	varchar(33)	char(1)	char(1)	char(1)	char(1)	char(1)\n" +
+			"null	jdbctst	CUSTOMERS	TABLE	null	null	null	null	null	null\n" +
+			"null	jdbctst	LINES	TABLE	null	null	null	null	null	null\n" +
+			"null	jdbctst	ORDERS	TABLE	null	null	null	null	null	null\n" +
+			"null	jdbctst	fk2c	TABLE	null	null	null	null	null	null\n" +
+			"null	jdbctst	nopk_twoucs	TABLE	jdbctst.nopk_twoucs table comment	null	null	null	null	null\n" +
+			"null	jdbctst	pk2c	TABLE	null	null	null	null	null	null\n" +
+			"null	jdbctst	pk_uc	TABLE	jdbctst.pk_uc table comment	null	null	null	null	null\n");
+
+			compareResultSet(dbmd.getTables(null, "jdbctst", "schemas", null), "getTables(null, jdbctst, schemas, null)",
+			"Resultset with 10 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION\n" +
+			"char(1)	varchar	varchar	varchar	varchar	char(1)	char(1)	char(1)	char(1)	char(1)\n");
+
+			compareResultSet(dbmd.getColumns(null, "jdbctst", "pk\\_uc", null), "getColumns(null, jdbctst, pk\\_uc, null)",
+			"Resultset with 24 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	NUM_PREC_RADIX	NULLABLE	REMARKS	COLUMN_DEF	SQL_DATA_TYPE	SQL_DATETIME_SUB	CHAR_OCTET_LENGTH	ORDINAL_POSITION	IS_NULLABLE	SCOPE_CATALOG	SCOPE_SCHEMA	SCOPE_TABLE	SOURCE_DATA_TYPE	IS_AUTOINCREMENT	IS_GENERATEDCOLUMN\n" +
+			"char(1)	varchar(7)	varchar(5)	varchar(5)	int	varchar(7)	int	int	int	int	int	varchar	varchar	int	int	int	int	varchar(3)	char(1)	char(1)	char(1)	smallint	char(3)	varchar(2)\n" +
+			"null	jdbctst	pk_uc	id1	4	int	32	0	0	2	0	null	null	0	0	null	1	NO	null	null	null	null	NO	NO\n" +
+			"null	jdbctst	pk_uc	name1	12	varchar	99	0	0	0	1	null	null	0	0	99	2	YES	null	null	null	null	NO	NO\n");
+
+			compareResultSet(dbmd.getPrimaryKeys(null, "jdbctst", "pk\\_uc"), "getPrimaryKeys(null, jdbctst, pk\\_uc)",
+			"Resultset with 6 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	KEY_SEQ	PK_NAME\n" +
+			"char(1)	varchar(7)	varchar(5)	varchar(3)	smallint	varchar(14)\n" +
+			"null	jdbctst	pk_uc	id1	1	pk_uc_id1_pkey\n");
+
+/* MvD: hier verder */
+			compareResultSet(dbmd.getPrimaryKeys(null, "tmp", "tmp_pk_uc"), "getPrimaryKeys(null, tmp, tmp_pk_uc)",
+			"Resultset with 6 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	KEY_SEQ	PK_NAME\n" +
+			"char(1)	varchar(3)	varchar(9)	varchar(3)	smallint	varchar(18)\n" +
+			"null	tmp	tmp_pk_uc	id1	1	tmp_pk_uc_id1_pkey\n");
+
+			compareResultSet(dbmd.getPrimaryKeys(null, "tmp", "glbl_pk_uc"), "getPrimaryKeys(null, tmp, glbl_pk_uc)",
+			"Resultset with 6 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	KEY_SEQ	PK_NAME\n" +
+			"char(1)	varchar(3)	varchar(10)	varchar(3)	smallint	varchar(19)\n" +
+			"null	tmp	glbl_pk_uc	id1	1	glbl_pk_uc_id1_pkey\n");
+
+			compareResultSet(dbmd.getExportedKeys(null, "sys", "table\\_types"), "getExportedKeys(null, sys, table\\_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n" +
+			"char(1)	varchar	varchar	varchar	char(1)	varchar	varchar	varchar	smallint	smallint	smallint	varchar	varchar	smallint\n");
+
+			compareResultSet(dbmd.getCrossReference(null, "sys", "tables", null, "sys", "table\\_types"), "getCrossReference(null, sys, tables, null, sys, table\\_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n" +
+			"char(1)	varchar	varchar	varchar	char(1)	varchar	varchar	varchar	smallint	smallint	smallint	varchar	varchar	smallint\n");
+
+			compareResultSet(dbmd.getImportedKeys(null, "sys", "table\\_types"), "getImportedKeys(null, sys, table\\_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY\n" +
+			"char(1)	varchar	varchar	varchar	char(1)	varchar	varchar	varchar	smallint	smallint	smallint	varchar	varchar	smallint\n");
+
+			compareResultSet(dbmd.getIndexInfo(null, "sys", "key_types", false, false), "getIndexInfo(null, sys, key_types, false, false)",
+			"Resultset with 13 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	NON_UNIQUE	INDEX_QUALIFIER	INDEX_NAME	TYPE	ORDINAL_POSITION	COLUMN_NAME	ASC_OR_DESC	CARDINALITY	PAGES	FILTER_CONDITION\n" +
+			"char(1)	varchar(3)	varchar(9)	boolean	char(1)	varchar(30)	tinyint	smallint	varchar(13)	char(1)	int	int	char(1)\n" +
+			"null	sys	key_types	false	null	key_types_key_type_id_pkey	2	1	key_type_id	null	3	0	null\n" +
+			"null	sys	key_types	false	null	key_types_key_type_name_unique	2	1	key_type_name	null	3	0	null\n");
+
+			compareResultSet(dbmd.getIndexInfo(null, "tmp", "tmp_pk_uc", false, false), "getIndexInfo(null, tmp, tmp_pk_uc, false, false)",
+			"Resultset with 13 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	NON_UNIQUE	INDEX_QUALIFIER	INDEX_NAME	TYPE	ORDINAL_POSITION	COLUMN_NAME	ASC_OR_DESC	CARDINALITY	PAGES	FILTER_CONDITION\n" +
+			"char(1)	varchar(3)	varchar(9)	boolean	char(1)	varchar(22)	tinyint	smallint	varchar(5)	char(1)	int	int	char(1)\n" +
+			"null	tmp	tmp_pk_uc	false	null	tmp_pk_uc_id1_pkey	2	1	id1	null	0	0	null\n" +
+			"null	tmp	tmp_pk_uc	false	null	tmp_pk_uc_name1_unique	2	1	name1	null	0	0	null\n" +
+			"null	tmp	tmp_pk_uc	true	null	tmp_pk_uc_i	2	1	id1	null	0	0	null\n" +
+			"null	tmp	tmp_pk_uc	true	null	tmp_pk_uc_i	2	2	name1	null	0	0	null\n");
+
+			compareResultSet(dbmd.getIndexInfo(null, "tmp", "glbl_pk_uc", false, false), "getIndexInfo(null, tmp, glbl_pk_uc, false, false)",
+			"Resultset with 13 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	NON_UNIQUE	INDEX_QUALIFIER	INDEX_NAME	TYPE	ORDINAL_POSITION	COLUMN_NAME	ASC_OR_DESC	CARDINALITY	PAGES	FILTER_CONDITION\n" +
+			"char(1)	varchar(3)	varchar(10)	boolean	char(1)	varchar(23)	tinyint	smallint	varchar(5)	char(1)	int	int	char(1)\n" +
+			"null	tmp	glbl_pk_uc	false	null	glbl_pk_uc_id1_pkey	2	1	id1	null	0	0	null\n" +
+			"null	tmp	glbl_pk_uc	false	null	glbl_pk_uc_name1_unique	2	1	name1	null	0	0	null\n" +
+			"null	tmp	glbl_pk_uc	true	null	glbl_pk_uc_i	2	1	id1	null	0	0	null\n" +
+			"null	tmp	glbl_pk_uc	true	null	glbl_pk_uc_i	2	2	name1	null	0	0	null\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "sys", "function_languages", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, sys, function_languages, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(11)	int	varchar(8)	int	int	smallint	smallint\n" +
+			"2	language_id	5	smallint	16	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "jdbctst", "nopk_twoucs", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, jdbctst, nopk_twoucs, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "jdbctst", "nopk_twoucs", DatabaseMetaData.bestRowTransaction, false),
+						"getBestRowIdentifier(null, jdbctst, nopk_twoucs, DatabaseMetaData.bestRowTransaction, false)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "tmp_pk_uc", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, tmp, tmp_pk_uc, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id1	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "tmp_nopk_twoucs", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, tmp, tmp_nopk_twoucs, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "tmp_nopk_twoucs", DatabaseMetaData.bestRowTransaction, false),
+						"getBestRowIdentifier(null, tmp, tmp_nopk_twoucs, DatabaseMetaData.bestRowTransaction, false)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "glbl_pk_uc", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, tmp, glbl_pk_uc, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id1	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "glbl_nopk_twoucs", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, tmp, glbl_nopk_twoucs, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "tmp", "glbl_nopk_twoucs", DatabaseMetaData.bestRowTransaction, false),
+						"getBestRowIdentifier(null, tmp, glbl_nopk_twoucs, DatabaseMetaData.bestRowTransaction, false)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(3)	int	varchar(3)	int	int	smallint	smallint\n" +
+			"2	id2	4	int	32	0	0	1\n");
+
+			// also test a table without pk or uc, it should return a row for each column
+			compareResultSet(dbmd.getBestRowIdentifier(null, "sys", "schemas", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, sys, schemas, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(13)	int	varchar(7)	int	int	smallint	smallint\n" +
+			"2	id	4	int	32	0	0	1\n" +
+			"2	name	12	varchar	1024	0	0	1\n" +
+			"2	authorization	4	int	32	0	0	1\n" +
+			"2	owner	4	int	32	0	0	1\n" +
+			"2	system	16	boolean	1	0	0	1\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "sys", "_tables", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, sys, _tables, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(13)	int	varchar(8)	int	int	smallint	smallint\n" +
+			"2	id	4	int	32	0	0	1\n" +
+			"2	name	12	varchar	1024	0	0	1\n" +
+			"2	schema_id	4	int	32	0	0	1\n" +
+			"2	query	12	varchar	1048576	0	0	1\n" +
+			"2	type	5	smallint	16	0	0	1\n" +
+			"2	system	16	boolean	1	0	0	1\n" +
+			"2	commit_action	5	smallint	16	0	0	1\n" +
+			"2	access	5	smallint	16	0	0	1\n");
+
+			// also test a view (without pk or uc of course), it should return no rows
+			compareResultSet(dbmd.getBestRowIdentifier(null, "sys", "tables", DatabaseMetaData.bestRowTransaction, true),
+						"getBestRowIdentifier(null, sys, tables, DatabaseMetaData.bestRowTransaction, true)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar	int	varchar	int	int	smallint	smallint\n");
+
+			compareResultSet(dbmd.getBestRowIdentifier(null, "sys", "table\\_types", DatabaseMetaData.bestRowTransaction, false),
+						"getBestRowIdentifier(null, sys, table\\_types, DatabaseMetaData.bestRowTransaction, false)",
+			"Resultset with 8 columns\n" +
+			"SCOPE	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	PSEUDO_COLUMN\n" +
+			"smallint	varchar(13)	int	varchar(8)	int	int	smallint	smallint\n" +
+			"2	table_type_id	5	smallint	16	0	0	1\n");
+
+			compareResultSet(dbmd.getTablePrivileges(null, "sys", "table\\_types"), "getTablePrivileges(null, sys, table\\_types)",
+			"Resultset with 7 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE\n" +
+			"char(1)	varchar(3)	varchar(11)	varchar(7)	varchar(6)	varchar(6)	varchar(2)\n" +
+			"null	sys	table_types	monetdb	public	SELECT	NO\n");
+
+			compareResultSet(dbmd.getColumnPrivileges(null, "sys", "table\\_types", null), "getColumnPrivileges(null, sys, table\\_types, null)",
+			"Resultset with 8 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE\n" +
+			"char(1)	varchar	varchar	varchar	varchar	varchar	varchar	varchar\n");
+
+			sb.setLength(0);	// clear the output log buffer
+		} catch (SQLException e) {
+			sb.setLength(0);	// clear the output log buffer
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		// cleanup created db objects
+		action = "Drop";
+		objtype = "index";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc_i", "DROP INDEX jdbctst.pk_uc_i;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc_i;", "DROP INDEX tmp.tmp_pk_uc_i;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_pk_uc_i", "DROP INDEX tmp.glbl_pk_uc_i;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs_i", "DROP INDEX jdbctst.nopk_twoucs_i;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_nopk_twoucs_i", "DROP INDEX tmp.tmp_nopk_twoucs_i;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_nopk_twoucs_i", "DROP INDEX tmp.glbl_nopk_twoucs_i;");
+		objtype = "table";
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk_uc", "DROP TABLE jdbctst.pk_uc;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_pk_uc", "DROP TABLE tmp.tmp_pk_uc;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_pk_uc", "DROP TABLE tmp.glbl_pk_uc;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.nopk_twoucs", "DROP TABLE jdbctst.nopk_twoucs;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.tmp_nopk_twoucs", "DROP TABLE tmp.tmp_nopk_twoucs;");
+		handleExecuteDDL(stmt, action, objtype, "tmp.glbl_nopk_twoucs", "DROP TABLE tmp.glbl_nopk_twoucs;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.\"LINES\"", "DROP TABLE jdbctst.\"LINES\";");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.\"ORDERS\"", "DROP TABLE jdbctst.\"ORDERS\";");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.\"CUSTOMERS\"", "DROP TABLE jdbctst.\"CUSTOMERS\";");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.fk2c", "DROP TABLE jdbctst.fk2c;");
+		handleExecuteDDL(stmt, action, objtype, "jdbctst.pk2c", "DROP TABLE jdbctst.pk2c;");
+
+		// All tables in schema jdbctst should now be gone, else we missed some DROP statements
+		try {
+			compareResultSet(dbmd.getTables(null, "jdbctst", "%%", null), "getTables(null, jdbctst, '%%', null)",
+			"Resultset with 10 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION\n" +
+			"char(1)	varchar	varchar	varchar	varchar	char(1)	char(1)	char(1)	char(1)	char(1)\n");
+			sb.setLength(0);	// clear the output log buffer
+		} catch (SQLException e) {
+			sb.setLength(0);	// clear the output log buffer
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		handleExecuteDDL(stmt, action, "schema", "jdbctst", "SET SCHEMA sys; DROP SCHEMA jdbctst;");
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Dmetadata", "");
+	}
+
 	private void Test_FetchSize() {
 		sb.setLength(0);	// clear the output log buffer