From 66a51df9bf956e97964bc8f957c69ae9751f7172 Mon Sep 17 00:00:00 2001 From: rusher Date: Tue, 9 Jul 2024 16:18:07 +0200 Subject: [PATCH] [CONJ-1180] improve DatabaseMeta.getExportedKeys() performances --- .../org/mariadb/jdbc/DatabaseMetaData.java | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java b/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java index 3b8cac522..4f79ea76f 100644 --- a/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java +++ b/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java @@ -406,6 +406,11 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) throw new SQLException("'table' parameter in getImportedKeys cannot be null"); } String database = conf.useCatalogTerm() == CatalogTerm.UseCatalog ? catalog : schema; + boolean getImportedKeysUsingIs = + Boolean.parseBoolean( + conf.nonMappedOptions().getProperty("getImportedKeysUsingIs", "false")); + + if (getImportedKeysUsingIs) return getImportedKeysUsingInformationSchema(database, table); try { return getImportedKeysUsingShowCreateTable(database, table); @@ -957,6 +962,205 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) if (table == null || table.isEmpty()) { throw new SQLException("'table' parameter in getExportedKeys cannot be null"); } + + boolean getExportedKeysUsingIs = + Boolean.parseBoolean( + conf.nonMappedOptions().getProperty("getExportedKeysUsingIs", "false")); + + if (getExportedKeysUsingIs) + return getExportedKeysUsingInformationSchema(catalog, schema, table); + + try { + return getExportedKeysUsingShowCreateTable(catalog, schema, table); + } catch (Exception e) { + // since 3.4.1 show create is now used by default, and there is no reason to have parsing + // exception, but cannot remove that try-catch in a correction release. + // TODO to be removed next minor version + return getExportedKeysUsingInformationSchema(catalog, schema, table); + } + } + + private ResultSet getExportedKeysUsingShowCreateTable(String catalog, String schema, String table) + throws Exception { + + String[] columnNames = { + "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" + }; + DataType[] dataTypes = { + DataType.VARCHAR, DataType.NULL, DataType.VARCHAR, + DataType.VARCHAR, DataType.VARCHAR, DataType.NULL, + DataType.VARCHAR, DataType.VARCHAR, DataType.SMALLINT, + DataType.SMALLINT, DataType.SMALLINT, DataType.VARCHAR, + DataType.VARCHAR, DataType.SMALLINT + }; + + String database = conf.useCatalogTerm() == CatalogTerm.UseCatalog ? catalog : schema; + + List data = new ArrayList<>(); + Statement stmt = connection.createStatement(); + + List databases = new ArrayList<>(); + + ResultSet rs = stmt.executeQuery("SHOW DATABASES"); + while (rs.next()) databases.add(rs.getString(1)); + + String ext = (database == null ? "" : quoteIdentifier(database) + ".") + quoteIdentifier(table); + Map externalInfo = getExtImportedKeys(ext, connection); + + for (String db : databases) { + if (db.equalsIgnoreCase("sys") + || db.equalsIgnoreCase("information_schema") + || db.equalsIgnoreCase("mysql") + || db.equalsIgnoreCase("performance_schema")) continue; + List tables = new ArrayList<>(); + ResultSet rsTables = stmt.executeQuery("SHOW TABLES FROM " + quoteIdentifier(db)); + while (rsTables.next()) tables.add(rsTables.getString(1)); + + for (String tableName : tables) { + try { + rs = + stmt.executeQuery( + "SHOW CREATE TABLE " + quoteIdentifier(db) + "." + quoteIdentifier(tableName)); + rs.next(); + String tableDef = rs.getString(2); + + String[] parts = tableDef.split("\n"); + for (String part : parts) { + part = part.trim(); + if (!part.toUpperCase(Locale.ROOT).startsWith("CONSTRAINT") + && !part.toUpperCase(Locale.ROOT).contains("FOREIGN KEY")) { + continue; + } + char[] partChar = part.toCharArray(); + + Identifier constraintName = new Identifier(); + + int pos = skipKeyword(partChar, 0, "CONSTRAINT"); + pos = parseIdentifier(partChar, pos, constraintName); + pos = skipKeyword(partChar, pos, "FOREIGN KEY"); + List foreignKeyCols = new ArrayList<>(); + pos = parseIdentifierList(partChar, pos, foreignKeyCols); + pos = skipKeyword(partChar, pos, "REFERENCES"); + Identifier pkTable = new Identifier(); + pos = parseIdentifier(partChar, pos, pkTable); + List primaryKeyCols = new ArrayList<>(); + parseIdentifierList(partChar, pos, primaryKeyCols); + String pkTableSchema = pkTable.schema != null ? pkTable.schema : db; + if (database != null && !database.equals(pkTableSchema)) continue; + if (!pkTable.name.equals(table)) continue; + int onUpdateReferenceAction = java.sql.DatabaseMetaData.importedKeyRestrict; + int onDeleteReferenceAction = java.sql.DatabaseMetaData.importedKeyRestrict; + + for (String referenceAction : + new String[] {"RESTRICT", "CASCADE", "SET NULL", "NO ACTION", "SET DEFAULT"}) { + if (part.toUpperCase(Locale.ROOT).contains("ON UPDATE " + referenceAction)) { + onUpdateReferenceAction = getImportedKeyAction(referenceAction); + } + if (part.toUpperCase(Locale.ROOT).contains("ON DELETE " + referenceAction)) { + onDeleteReferenceAction = getImportedKeyAction(referenceAction); + } + } + + for (int i = 0; i < primaryKeyCols.size(); i++) { + String[] row = new String[14]; + row[0] = + conf.useCatalogTerm() == CatalogTerm.UseCatalog + ? (pkTable.schema == null ? database : pkTable.schema) + : "def"; // PKTABLE_CAT + row[1] = + conf.useCatalogTerm() == CatalogTerm.UseSchema + ? (pkTable.schema == null ? database : pkTable.schema) + : null; // PKTABLE_SCHEM + row[2] = pkTable.name; // PKTABLE_NAME + row[3] = primaryKeyCols.get(i).name; // PKCOLUMN_NAME + row[4] = + conf.useCatalogTerm() == CatalogTerm.UseCatalog ? database : "def"; // FKTABLE_CAT + row[5] = + conf.useCatalogTerm() == CatalogTerm.UseSchema ? database : null; // FKTABLE_SCHEM + row[6] = tableName; // FKTABLE_NAME + row[7] = foreignKeyCols.get(i).name; // FKCOLUMN_NAME + row[8] = Integer.toString(i + 1); // KEY_SEQ + row[9] = Integer.toString(onUpdateReferenceAction); // UPDATE_RULE + row[10] = Integer.toString(onDeleteReferenceAction); // DELETE_RULE + row[11] = constraintName.name; // FK_NAME + row[12] = null; // PK_NAME + + for (Map.Entry entry : externalInfo.entrySet()) { + boolean foundAll = true; + List pkKeysFound = new ArrayList<>(); + + for (String keyPart : entry.getKey()) { + boolean foundKey = false; + for (Identifier keyCol : primaryKeyCols) { + if (keyCol.name.equals(keyPart)) { + foundKey = true; + pkKeysFound.add(keyCol); + break; + } + } + if (!foundKey) { + foundAll = false; + break; + } + } + // either found all the keys, or found at least all the one required + if (foundAll || pkKeysFound.size() == primaryKeyCols.size()) { + row[12] = entry.getValue(); + } + } + + row[13] = + Integer.toString(DatabaseMetaData.importedKeyNotDeferrable); // DEFERRABILITY + data.add(row); + } + } + + } catch (SQLException e) { + // eat + } + } + } + + String[][] arr = data.toArray(new String[0][]); + /* Sort array by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, PK_NAME, KEY_SEQ.*/ + Arrays.sort( + arr, + (row1, row2) -> { + int result = 0; + if (row1[4] != null) result = row1[4].compareTo(row2[4]); // FKTABLE_CAT + if (result == 0) { + if (row1[5] != null) result = row1[5].compareTo(row2[5]); // FKTABLE_SCHEM + if (result == 0) { + result = row1[6].compareTo(row2[6]); // PKTABLE_NAME + if (result == 0) { + if (row1[12] != null) + result = row1[12].compareTo(row2[12]); // adding PK_NAME for reliability + if (result == 0) { + result = row1[8].length() - row2[8].length(); // KEY_SEQ + if (result == 0) { + result = row1[8].compareTo(row2[8]); + } + } + } + } + } + return result; + }); + return CompleteResult.createResultSet( + columnNames, + dataTypes, + arr, + connection.getContext(), + ColumnFlags.PRIMARY_KEY, + ResultSet.TYPE_SCROLL_INSENSITIVE); + } + + private ResultSet getExportedKeysUsingInformationSchema( + String catalog, String schema, String table) throws SQLException { StringBuilder sb = new StringBuilder("SELECT ") .append( @@ -1096,6 +1300,10 @@ public ResultSet getImportedKeysUsingShowCreateTable(final String database, Stri while (rs.next()) databases.add(rs.getString(1)); for (String db : databases) { + if (db.equalsIgnoreCase("sys") + || db.equalsIgnoreCase("information_schema") + || db.equalsIgnoreCase("mysql") + || db.equalsIgnoreCase("performance_schema")) continue; try { rs = stmt.executeQuery(