comparison 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
comparison
equal deleted inserted replaced
652:6a34d2c36dec 653:5eb9d54057e6
2418 * When there is No PK but there are multiple unique constraints, we need to pick one. 2418 * When there is No PK but there are multiple unique constraints, we need to pick one.
2419 * In the current implementation we return the first uc (lowest sys.keys.id). 2419 * In the current implementation we return the first uc (lowest sys.keys.id).
2420 * Instead of the first (in case of multiple) we should potentially use the uc which has 2420 * Instead of the first (in case of multiple) we should potentially use the uc which has
2421 * a) the least number of columns and 2421 * a) the least number of columns and
2422 * b) the smallest total(size in bytes). 2422 * b) the smallest total(size in bytes).
2423 * That's complex to built in SQL. 2423 * That's much more complex to do in SQL than the current implementation, which is fast and gives a correct result.
2424 */ 2424 */
2425 // 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) 2425
2426 2426 final StringBuilder query = new StringBuilder(3000);
2427 final StringBuilder query = new StringBuilder(2600); 2427 // 1st cte: syskeys
2428 query.append("with syskeys as (" + 2428 query.append("with syskeys as (" +
2429 // all pkeys 2429 // all pkeys
2430 "SELECT \"id\", \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" = 0 " + 2430 "SELECT \"id\", \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" = 0 " +
2431 "UNION ALL " + 2431 "UNION ALL " +
2432 // and first unique constraint of a table when table has no pkey 2432 // and first unique constraint of a table when table has no pkey
2433 "SELECT \"id\", \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" = 1 " + 2433 "SELECT \"id\", \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" = 1 " +
2434 "AND \"table_id\" NOT IN (select \"table_id\" from \"sys\".\"keys\" where \"type\" = 0) " + 2434 "AND \"table_id\" NOT IN (select \"table_id\" from \"sys\".\"keys\" where \"type\" = 0) " +
2435 "AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"sys\".\"keys\" where \"type\" = 1 group by \"table_id\"))"); 2435 "AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"sys\".\"keys\" where \"type\" = 1 group by \"table_id\"))");
2436 if (incltmpkey) { 2436 if (incltmpkey) {
2437 // we must also include the primary key or unique constraint of local temporary tables which are stored in tmp.keys 2437 // we must also include the primary key or unique constraint of local temporary tables which are stored in tmp.keys
2438 // 2nd cte: tmpkeys
2438 query.append(", tmpkeys as (" + 2439 query.append(", tmpkeys as (" +
2439 "SELECT \"id\", \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" = 0 " + 2440 "SELECT \"id\", \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" = 0 " +
2440 "UNION ALL " + 2441 "UNION ALL " +
2441 "SELECT \"id\", \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" = 1 " + 2442 "SELECT \"id\", \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" = 1 " +
2442 "AND \"table_id\" NOT IN (select \"table_id\" from \"tmp\".\"keys\" where \"type\" = 0) " + 2443 "AND \"table_id\" NOT IN (select \"table_id\" from \"tmp\".\"keys\" where \"type\" = 0) " +
2443 "AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"tmp\".\"keys\" where \"type\" = 1 group by \"table_id\"))"); 2444 "AND (\"table_id\", \"id\") IN (select \"table_id\", min(\"id\") from \"tmp\".\"keys\" where \"type\" = 1 group by \"table_id\"))");
2444 } 2445 }
2445 query.append(", cols as (" + 2446 // 3rd cte: tableids
2446 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " + 2447 query.append(", tableids as (" +
2447 "FROM syskeys k " + 2448 "SELECT t.\"id\" " +
2448 "JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " + 2449 "FROM \"sys\".\"tables\" t " +
2449 "JOIN \"sys\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
2450 "JOIN \"sys\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
2451 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + 2450 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
2452 "WHERE 1=1"); 2451 "WHERE t.\"type\" NOT IN (1, 11) "); // exclude all VIEWs and SYSTEM VIEWs
2453 if (catalog != null && !catalog.isEmpty()) { 2452 if (catalog != null && !catalog.isEmpty()) {
2454 // non-empty catalog selection. 2453 // non-empty catalog selection.
2455 // as we do not support catalogs this always results in no rows returned 2454 // as we do not support catalogs this always results in no rows returned
2456 query.append(" AND 1=0"); 2455 query.append("AND 1=0");
2457 } else { 2456 } else {
2458 if (scope == DatabaseMetaData.bestRowSession 2457 if (scope == DatabaseMetaData.bestRowSession
2459 || scope == DatabaseMetaData.bestRowTransaction 2458 || scope == DatabaseMetaData.bestRowTransaction
2460 || scope == DatabaseMetaData.bestRowTemporary) { 2459 || scope == DatabaseMetaData.bestRowTemporary) {
2461 if (schema != null) { 2460 if (schema != null) {
2462 // do not allow wildcard matching with LIKE, as the resultset does not include the schema info 2461 // do not allow wildcard matching with LIKE, as the resultset does not include the schema info
2463 query.append(" AND s.\"name\" = ").append(MonetWrapper.sq(schema)); 2462 query.append("AND s.\"name\" = ").append(MonetWrapper.sq(schema));
2464 } 2463 }
2465 if (table != null) { 2464 if (table != null) {
2466 // do not allow wildcard matching with LIKE, as the resultset does not include the table info 2465 // do not allow wildcard matching with LIKE, as the resultset does not include the table info
2467 query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table)); 2466 query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table));
2468 } 2467 }
2469 if (!nullable) {
2470 query.append(" AND c.\"null\" = false");
2471 }
2472 } else { 2468 } else {
2473 query.append(" AND 1=0"); 2469 query.append("AND 1=0");
2474 } 2470 }
2471 }
2472 // 4th cte: cols, this unions 2 (or 4 when incltmpkey == true) select queries
2473 query.append("), cols as (" +
2474 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " +
2475 "FROM syskeys k " +
2476 "JOIN tableids t ON k.\"table_id\" = t.\"id\" " +
2477 "JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2478 "JOIN \"sys\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\")");
2479 if (!nullable) {
2480 query.append(" WHERE c.\"null\" = false");
2475 } 2481 }
2476 if (incltmpkey) { 2482 if (incltmpkey) {
2477 // we must also include the primary key or unique constraint of local temporary tables 2483 // we must also include the primary key or unique constraint of local temporary tables
2478 // which are stored in tmp.keys, tmp.objects, tmp._tables and tmp._columns 2484 // which are stored in tmp.keys, tmp.objects, tmp._tables and tmp._columns
2479 query.append(" UNION ALL " + 2485 query.append(" UNION ALL " +
2480 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " + 2486 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", o.\"nr\" " +
2481 "FROM tmpkeys k " + 2487 "FROM tmpkeys k " +
2488 "JOIN tableids t ON k.\"table_id\" = t.\"id\" " +
2482 "JOIN \"tmp\".\"objects\" o ON k.\"id\" = o.\"id\" " + 2489 "JOIN \"tmp\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2483 "JOIN \"tmp\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " + 2490 "JOIN \"tmp\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\")");
2484 "JOIN \"tmp\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " + 2491 if (!nullable) {
2485 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + 2492 query.append(" WHERE c.\"null\" = false");
2486 "WHERE 1=1");
2487 if (catalog != null && !catalog.isEmpty()) {
2488 // non-empty catalog selection.
2489 // as we do not support catalogs this always results in no rows returned
2490 query.append(" AND 1=0");
2491 } else {
2492 if (scope == DatabaseMetaData.bestRowSession
2493 || scope == DatabaseMetaData.bestRowTransaction
2494 || scope == DatabaseMetaData.bestRowTemporary) {
2495 if (schema != null) {
2496 // do not allow wildcard matching with LIKE, as the resultset does not include the schema info
2497 query.append(" AND s.\"name\" = ").append(MonetWrapper.sq(schema));
2498 }
2499 if (table != null) {
2500 // do not allow wildcard matching with LIKE, as the resultset does not include the table info
2501 query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table));
2502 }
2503 if (!nullable) {
2504 query.append(" AND c.\"null\" = false");
2505 }
2506 } else {
2507 query.append(" AND 1=0");
2508 }
2509 } 2493 }
2510 } 2494 }
2495 // when there is No PK and No unique constraints, we should return all columns of the table
2496 // (else in SQuirreL no header is shown in the "Row IDs" tab)
2497 query.append(" UNION ALL " +
2498 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", c.\"number\" " +
2499 "FROM tableids t " +
2500 "JOIN \"sys\".\"_columns\" c ON t.\"id\" = c.\"table_id\" " +
2501 "WHERE t.\"id\" NOT IN (SELECT \"table_id\" FROM \"sys\".\"keys\" WHERE \"type\" in (0, 1))");
2502 if (!nullable) {
2503 query.append(" AND c.\"null\" = false");
2504 }
2505 if (incltmpkey) {
2506 query.append(" UNION ALL " +
2507 "SELECT c.\"name\", c.\"type\", c.\"type_digits\", c.\"type_scale\", c.\"number\" " +
2508 "FROM tableids t " +
2509 "JOIN \"tmp\".\"_columns\" c ON t.\"id\" = c.\"table_id\" " +
2510 "WHERE t.\"id\" NOT IN (SELECT \"table_id\" FROM \"tmp\".\"keys\" WHERE \"type\" in (0, 1))");
2511 if (!nullable) {
2512 query.append(" AND c.\"null\" = false");
2513 }
2514 }
2515 // the final select query
2511 query.append(") SELECT " + 2516 query.append(") SELECT " +
2512 "cast(").append(DatabaseMetaData.bestRowSession).append(" AS smallint) AS \"SCOPE\", " + 2517 "cast(").append(DatabaseMetaData.bestRowSession).append(" AS smallint) AS \"SCOPE\", " +
2513 "c.\"name\" AS \"COLUMN_NAME\", " + 2518 "c.\"name\" AS \"COLUMN_NAME\", " +
2514 "cast(").append(MonetDriver.getSQLTypeMap("c.\"type\"")).append(" AS int) AS \"DATA_TYPE\", " + 2519 "cast(").append(MonetDriver.getSQLTypeMap("c.\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
2515 "c.\"type\" AS \"TYPE_NAME\", " + 2520 "c.\"type\" AS \"TYPE_NAME\", " +