comparison src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java @ 649:060347aa81ea

By fixing methods getPrimaryKeys(), getBestRowIdentifier() and getIndexInfo() for local temporary tables, it also caused it to fail for global temporary tables in schema tmp. Corrected this, such that it now works for local and global temporary tables. Added tests.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Wed, 11 May 2022 17:34:00 +0200 (2022-05-11)
parents fbed03097738
children 6a34d2c36dec
comparison
equal deleted inserted replaced
648:03e0f577db00 649:060347aa81ea
2407 final String table, 2407 final String table,
2408 final int scope, 2408 final int scope,
2409 final boolean nullable 2409 final boolean nullable
2410 ) throws SQLException 2410 ) throws SQLException
2411 { 2411 {
2412 final String sysORtmp = (schema != null && "tmp".equals(schema)) ? "\"tmp\"" : "\"sys\"";
2413
2414 // first find out if the table has a Primary Key. If it does, we should return only those columns 2412 // first find out if the table has a Primary Key. If it does, we should return only those columns
2415 boolean hasPK = false; 2413 boolean hasPK = false;
2416 ResultSet pkey = null; 2414 ResultSet pkey = null;
2417 try { 2415 try {
2418 pkey = getPrimaryKeys(catalog, schema, table); 2416 pkey = getPrimaryKeys(catalog, schema, table);
2421 } 2419 }
2422 } catch (SQLException e) { 2420 } catch (SQLException e) {
2423 // ignore 2421 // ignore
2424 } finally { 2422 } finally {
2425 MonetConnection.closeResultsetStatement(pkey, null); 2423 MonetConnection.closeResultsetStatement(pkey, null);
2424 pkey = null;
2426 } 2425 }
2427 2426
2428 // TODO: when there is No PK and there are multiple unique constraints, pick only the unique constraint which has a) the least number of columns and b) the smallest total(size in bytes) 2427 // TODO: when there is No PK and there are multiple unique constraints, pick only the unique constraint which has a) the least number of columns and b) the smallest total(size in bytes)
2429 // 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) 2428 // 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)
2430 2429
2431 final StringBuilder query = new StringBuilder(1500); 2430 final StringBuilder query = new StringBuilder(3000);
2432 query.append("SELECT "); 2431 query.append("SELECT ");
2433 if (!hasPK) { 2432 if (!hasPK) {
2434 // Note: currently DISTINCT is needed to filter out possible duplicate column names when there are multiple unique constraints !! 2433 // Note: currently DISTINCT is needed to filter out possible duplicate column names when there are multiple unique constraints !!
2435 // TODO: when no PK and there are multiple unique constraints determine which one to select such that DISTINCT keyword is not needed anymore 2434 // TODO: when no PK and there are multiple unique constraints determine which one to select such that DISTINCT keyword is not needed anymore
2436 query.append("DISTINCT "); 2435 query.append("DISTINCT ");
2441 "c.\"type\" AS \"TYPE_NAME\", " + 2440 "c.\"type\" AS \"TYPE_NAME\", " +
2442 "c.\"type_digits\" AS \"COLUMN_SIZE\", " + 2441 "c.\"type_digits\" AS \"COLUMN_SIZE\", " +
2443 "cast(0 as int) AS \"BUFFER_LENGTH\", " + 2442 "cast(0 as int) AS \"BUFFER_LENGTH\", " +
2444 "cast(c.\"type_scale\" AS smallint) AS \"DECIMAL_DIGITS\", " + 2443 "cast(c.\"type_scale\" AS smallint) AS \"DECIMAL_DIGITS\", " +
2445 "cast(").append(DatabaseMetaData.bestRowNotPseudo).append(" AS smallint) AS \"PSEUDO_COLUMN\" " + 2444 "cast(").append(DatabaseMetaData.bestRowNotPseudo).append(" AS smallint) AS \"PSEUDO_COLUMN\" " +
2446 "FROM ").append(sysORtmp).append(".\"keys\" k " + 2445 "FROM \"sys\".\"keys\" k " +
2447 "JOIN ").append(sysORtmp).append(".\"objects\" o ON k.\"id\" = o.\"id\" " + 2446 "JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2448 "JOIN ").append(sysORtmp).append(".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " + 2447 "JOIN \"sys\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
2449 "JOIN ").append(sysORtmp).append(".\"_tables\" t ON k.\"table_id\" = t.\"id\" " + 2448 "JOIN \"sys\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
2450 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + 2449 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
2451 "WHERE k.\"type\" = ").append(hasPK ? "0" : "1"); // the primary key (type = 0) or else any unique key (type = 1) 2450 "WHERE k.\"type\" = ").append(hasPK ? "0" : "1"); // the primary key (type = 0) or else any unique key (type = 1)
2452 2451
2453 if (catalog != null && !catalog.isEmpty()) { 2452 if (catalog != null && !catalog.isEmpty()) {
2454 // non-empty catalog selection. 2453 // non-empty catalog selection.
2472 } else { 2471 } else {
2473 query.append(" AND 1=0"); 2472 query.append(" AND 1=0");
2474 } 2473 }
2475 } 2474 }
2476 2475
2476 final boolean includetmp = (schema == null)
2477 || (schema != null && ("tmp".equals(schema) || schema.contains("%") || schema.contains("_")));
2478 if (includetmp) {
2479 // 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
2480 query.append(" UNION ALL ");
2481 query.append("SELECT ");
2482 if (!hasPK) {
2483 // Note: currently DISTINCT is needed to filter out possible duplicate column names when there are multiple unique constraints !!
2484 // TODO: when no PK and there are multiple unique constraints determine which one to select such that DISTINCT keyword is not needed anymore
2485 query.append("DISTINCT ");
2486 }
2487 query.append("cast(").append(DatabaseMetaData.bestRowSession).append(" AS smallint) AS \"SCOPE\", " +
2488 "c.\"name\" AS \"COLUMN_NAME\", " +
2489 "cast(").append(MonetDriver.getSQLTypeMap("c.\"type\"")).append(" AS int) AS \"DATA_TYPE\", " +
2490 "c.\"type\" AS \"TYPE_NAME\", " +
2491 "c.\"type_digits\" AS \"COLUMN_SIZE\", " +
2492 "cast(0 as int) AS \"BUFFER_LENGTH\", " +
2493 "cast(c.\"type_scale\" AS smallint) AS \"DECIMAL_DIGITS\", " +
2494 "cast(").append(DatabaseMetaData.bestRowNotPseudo).append(" AS smallint) AS \"PSEUDO_COLUMN\" " +
2495 "FROM \"tmp\".\"keys\" k " +
2496 "JOIN \"tmp\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2497 "JOIN \"tmp\".\"_columns\" c ON (k.\"table_id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
2498 "JOIN \"tmp\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
2499 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
2500 "WHERE k.\"type\" = ").append(hasPK ? "0" : "1"); // the primary key (type = 0) or else any unique key (type = 1)
2501
2502 if (catalog != null && !catalog.isEmpty()) {
2503 // non-empty catalog selection.
2504 // as we do not support catalogs this always results in no rows returned
2505 query.append(" AND 1=0");
2506 } else {
2507 if (scope == DatabaseMetaData.bestRowSession
2508 || scope == DatabaseMetaData.bestRowTransaction
2509 || scope == DatabaseMetaData.bestRowTemporary) {
2510 if (schema != null) {
2511 // do not allow wildcard matching with LIKE, as the resultset does not include the schema info
2512 query.append(" AND s.\"name\" = ").append(MonetWrapper.sq(schema));
2513 }
2514 if (table != null) {
2515 // do not allow wildcard matching with LIKE, as the resultset does not include the table info
2516 query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(table));
2517 }
2518 if (!nullable) {
2519 query.append(" AND c.\"null\" = false");
2520 }
2521 } else {
2522 query.append(" AND 1=0");
2523 }
2524 }
2525 }
2526
2477 // was: query.append(" ORDER BY \"SCOPE\", o.\"nr\", \"COLUMN_NAME\""); 2527 // was: query.append(" ORDER BY \"SCOPE\", o.\"nr\", \"COLUMN_NAME\"");
2478 // But as of Jan2022 this ordering returns error: SELECT: with DISTINCT ORDER BY expressions must appear in select list 2528 // But as of Jan2022 this ordering returns error: SELECT: with DISTINCT ORDER BY expressions must appear in select list
2479 // so had to remove the o.\"nr\", part when there is No PKey. This means the columns are than ordered on names instead of creation order in their unique constraint definition 2529 // so had to remove the o.\"nr\", part when there is No PKey. This means the columns are than ordered on names instead of creation order in their unique constraint definition
2480 query.append(" ORDER BY \"SCOPE\", "); 2530 query.append(" ORDER BY \"SCOPE\", ");
2481 if (hasPK) 2531 if (hasPK && !includetmp)
2482 query.append("o.\"nr\", "); 2532 query.append("o.\"nr\", ");
2483 query.append("\"COLUMN_NAME\""); 2533 query.append("\"COLUMN_NAME\"");
2484 2534
2485 return executeMetaDataQuery(query.toString()); 2535 return executeMetaDataQuery(query.toString());
2486 } 2536 }
2561 final String catalog, 2611 final String catalog,
2562 final String schema, 2612 final String schema,
2563 final String table 2613 final String table
2564 ) throws SQLException 2614 ) throws SQLException
2565 { 2615 {
2566 final String sysORtmp = (schema != null && "tmp".equals(schema)) ? "\"tmp\"" : "\"sys\""; 2616 final StringBuilder query = new StringBuilder(1200);
2567 final StringBuilder query = new StringBuilder(600);
2568 query.append("SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " + 2617 query.append("SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " +
2569 "s.\"name\" AS \"TABLE_SCHEM\", " + 2618 "s.\"name\" AS \"TABLE_SCHEM\", " +
2570 "t.\"name\" AS \"TABLE_NAME\", " + 2619 "t.\"name\" AS \"TABLE_NAME\", " +
2571 "o.\"name\" AS \"COLUMN_NAME\", " + 2620 "o.\"name\" AS \"COLUMN_NAME\", " +
2572 "cast(1 + o.\"nr\" AS smallint) AS \"KEY_SEQ\", " + 2621 "cast(1 + o.\"nr\" AS smallint) AS \"KEY_SEQ\", " +
2573 " k.\"name\" AS \"PK_NAME\" " + 2622 "k.\"name\" AS \"PK_NAME\" " +
2574 "FROM ").append(sysORtmp).append(".\"keys\" k " + 2623 "FROM \"sys\".\"keys\" k " +
2575 "JOIN ").append(sysORtmp).append(".\"objects\" o ON k.\"id\" = o.\"id\" " + 2624 "JOIN \"sys\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2576 "JOIN ").append(sysORtmp).append(".\"_tables\" t ON k.\"table_id\" = t.\"id\" " + 2625 "JOIN \"sys\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
2577 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + 2626 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
2578 "WHERE k.\"type\" = 0"); // only primary keys (type = 0) 2627 "WHERE k.\"type\" = 0"); // only primary keys (type = 0)
2579 2628
2580 if (catalog != null && !catalog.isEmpty()) { 2629 if (catalog != null && !catalog.isEmpty()) {
2581 // non-empty catalog selection. 2630 // non-empty catalog selection.
2585 if (schema != null && !schema.equals("%")) { 2634 if (schema != null && !schema.equals("%")) {
2586 query.append(" AND s.\"name\" ").append(composeMatchPart(schema)); 2635 query.append(" AND s.\"name\" ").append(composeMatchPart(schema));
2587 } 2636 }
2588 if (table != null && !table.equals("%")) { 2637 if (table != null && !table.equals("%")) {
2589 query.append(" AND t.\"name\" ").append(composeMatchPart(table)); 2638 query.append(" AND t.\"name\" ").append(composeMatchPart(table));
2639 }
2640 }
2641
2642 final boolean includetmp = (schema == null)
2643 || (schema != null && ("tmp".equals(schema) || schema.contains("%") || schema.contains("_")));
2644 if (includetmp) {
2645 // we must also include the keys of local temporary tables which are stored in tmp.keys, tmp.objects and tmp._tables
2646 query.append(" UNION ALL ");
2647 query.append("SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " +
2648 "s.\"name\" AS \"TABLE_SCHEM\", " +
2649 "t.\"name\" AS \"TABLE_NAME\", " +
2650 "o.\"name\" AS \"COLUMN_NAME\", " +
2651 "cast(1 + o.\"nr\" AS smallint) AS \"KEY_SEQ\", " +
2652 "k.\"name\" AS \"PK_NAME\" " +
2653 "FROM \"tmp\".\"keys\" k " +
2654 "JOIN \"tmp\".\"objects\" o ON k.\"id\" = o.\"id\" " +
2655 "JOIN \"tmp\".\"_tables\" t ON k.\"table_id\" = t.\"id\" " +
2656 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
2657 "WHERE k.\"type\" = 0"); // only primary keys (type = 0)
2658
2659 if (catalog != null && !catalog.isEmpty()) {
2660 // non-empty catalog selection.
2661 // as we do not support catalogs this always results in no rows returned
2662 query.append(" AND 1=0");
2663 } else {
2664 if (schema != null && !schema.equals("%")) {
2665 query.append(" AND s.\"name\" ").append(composeMatchPart(schema));
2666 }
2667 if (table != null && !table.equals("%")) {
2668 query.append(" AND t.\"name\" ").append(composeMatchPart(table));
2669 }
2590 } 2670 }
2591 } 2671 }
2592 2672
2593 query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"COLUMN_NAME\""); 2673 query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\", \"COLUMN_NAME\"");
2594 2674
3079 } finally { 3159 } finally {
3080 MonetConnection.closeResultsetStatement(count, null); 3160 MonetConnection.closeResultsetStatement(count, null);
3081 } 3161 }
3082 } 3162 }
3083 3163
3084 final String sysORtmp = (schema != null && "tmp".equals(schema)) ? "\"tmp\"" : "\"sys\""; 3164 final StringBuilder query = new StringBuilder(2500);
3085 final StringBuilder query = new StringBuilder(1250);
3086 query.append( 3165 query.append(
3087 "SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " + 3166 "SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " +
3088 "s.\"name\" AS \"TABLE_SCHEM\", " + 3167 "s.\"name\" AS \"TABLE_SCHEM\", " +
3089 "t.\"name\" AS \"TABLE_NAME\", " + 3168 "t.\"name\" AS \"TABLE_NAME\", " +
3090 "CASE WHEN k.\"name\" IS NULL THEN true ELSE false END AS \"NON_UNIQUE\", " + 3169 "CASE WHEN k.\"name\" IS NULL THEN true ELSE false END AS \"NON_UNIQUE\", " +
3095 "c.\"name\" AS \"COLUMN_NAME\", " + 3174 "c.\"name\" AS \"COLUMN_NAME\", " +
3096 "cast(null AS char(1)) AS \"ASC_OR_DESC\", " + // sort sequence currently not supported in keys or indexes in MonetDB 3175 "cast(null AS char(1)) AS \"ASC_OR_DESC\", " + // sort sequence currently not supported in keys or indexes in MonetDB
3097 "cast(").append(table_row_count).append(" AS int) AS \"CARDINALITY\", " + 3176 "cast(").append(table_row_count).append(" AS int) AS \"CARDINALITY\", " +
3098 "cast(0 AS int) AS \"PAGES\", " + 3177 "cast(0 AS int) AS \"PAGES\", " +
3099 "cast(null AS char(1)) AS \"FILTER_CONDITION\" " + 3178 "cast(null AS char(1)) AS \"FILTER_CONDITION\" " +
3100 "FROM ").append(sysORtmp).append(".\"idxs\" i " + 3179 "FROM \"sys\".\"idxs\" i " +
3101 "JOIN ").append(sysORtmp).append(".\"_tables\" t ON i.\"table_id\" = t.\"id\" " + 3180 "JOIN \"sys\".\"_tables\" t ON i.\"table_id\" = t.\"id\" " +
3102 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + 3181 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
3103 "JOIN ").append(sysORtmp).append(".\"objects\" o ON i.\"id\" = o.\"id\" " + 3182 "JOIN \"sys\".\"objects\" o ON i.\"id\" = o.\"id\" " +
3104 "JOIN ").append(sysORtmp).append(".\"_columns\" c ON (t.\"id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " + 3183 "JOIN \"sys\".\"_columns\" c ON (t.\"id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
3105 "LEFT OUTER JOIN ").append(sysORtmp).append(".\"keys\" k ON (i.\"name\" = k.\"name\" AND i.\"table_id\" = k.\"table_id\" AND k.\"type\" IN (0,1)) "); // primary (0) and unique keys (1) only 3184 "LEFT OUTER JOIN \"sys\".\"keys\" k ON (i.\"name\" = k.\"name\" AND i.\"table_id\" = k.\"table_id\" AND k.\"type\" IN (0,1)) "); // primary (0) and unique keys (1) only
3106 3185
3107 if (catalog != null && !catalog.isEmpty()) { 3186 if (catalog != null && !catalog.isEmpty()) {
3108 // non-empty catalog selection. 3187 // non-empty catalog selection.
3109 // as we do not support catalogs this always results in no rows returned 3188 // as we do not support catalogs this always results in no rows returned
3110 query.append("WHERE 1=0"); 3189 query.append("WHERE 1=0");
3120 needWhere = false; 3199 needWhere = false;
3121 } 3200 }
3122 if (unique) { 3201 if (unique) {
3123 query.append(needWhere ? "WHERE" : " AND") 3202 query.append(needWhere ? "WHERE" : " AND")
3124 .append(" k.\"name\" IS NOT NULL"); 3203 .append(" k.\"name\" IS NOT NULL");
3204 }
3205 }
3206
3207 final boolean includetmp = (schema == null)
3208 || (schema != null && ("tmp".equals(schema) || schema.contains("%") || schema.contains("_")));
3209 if (includetmp) {
3210 // we must also include the indexes of local temporary tables which are stored in tmp.idxs, tmp._tables, tmp._columns, tmp.objects and tmp.keys
3211 query.append(" UNION ALL ");
3212 query.append(
3213 "SELECT cast(null AS char(1)) AS \"TABLE_CAT\", " +
3214 "s.\"name\" AS \"TABLE_SCHEM\", " +
3215 "t.\"name\" AS \"TABLE_NAME\", " +
3216 "CASE WHEN k.\"name\" IS NULL THEN true ELSE false END AS \"NON_UNIQUE\", " +
3217 "cast(null AS char(1)) AS \"INDEX_QUALIFIER\", " +
3218 "i.\"name\" AS \"INDEX_NAME\", " +
3219 "CASE i.\"type\" WHEN 0 THEN ").append(DatabaseMetaData.tableIndexHashed).append(" ELSE ").append(DatabaseMetaData.tableIndexOther).append(" END AS \"TYPE\", " +
3220 "cast(o.\"nr\" +1 AS smallint) AS \"ORDINAL_POSITION\", "+
3221 "c.\"name\" AS \"COLUMN_NAME\", " +
3222 "cast(null AS char(1)) AS \"ASC_OR_DESC\", " + // sort sequence currently not supported in keys or indexes in MonetDB
3223 "cast(").append(table_row_count).append(" AS int) AS \"CARDINALITY\", " +
3224 "cast(0 AS int) AS \"PAGES\", " +
3225 "cast(null AS char(1)) AS \"FILTER_CONDITION\" " +
3226 "FROM \"tmp\".\"idxs\" i " +
3227 "JOIN \"tmp\".\"_tables\" t ON i.\"table_id\" = t.\"id\" " +
3228 "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
3229 "JOIN \"tmp\".\"objects\" o ON i.\"id\" = o.\"id\" " +
3230 "JOIN \"tmp\".\"_columns\" c ON (t.\"id\" = c.\"table_id\" AND o.\"name\" = c.\"name\") " +
3231 "LEFT OUTER JOIN \"tmp\".\"keys\" k ON (i.\"name\" = k.\"name\" AND i.\"table_id\" = k.\"table_id\" AND k.\"type\" IN (0,1)) "); // primary (0) and unique keys (1) only
3232
3233 if (catalog != null && !catalog.isEmpty()) {
3234 // non-empty catalog selection.
3235 // as we do not support catalogs this always results in no rows returned
3236 query.append("WHERE 1=0");
3237 } else {
3238 boolean needWhere = true;
3239 if (schema != null && !schema.equals("%")) {
3240 query.append("WHERE s.\"name\" ").append(composeMatchPart(schema));
3241 needWhere = false;
3242 }
3243 if (table != null && !table.equals("%")) {
3244 query.append(needWhere ? "WHERE" : " AND")
3245 .append(" t.\"name\" ").append(composeMatchPart(table));
3246 needWhere = false;
3247 }
3248 if (unique) {
3249 query.append(needWhere ? "WHERE" : " AND")
3250 .append(" k.\"name\" IS NOT NULL");
3251 }
3125 } 3252 }
3126 } 3253 }
3127 3254
3128 query.append(" ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\""); 3255 query.append(" ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\"");
3129 3256