diff src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java @ 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 2448ce017593
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\", " +