Skip to content

Commit

Permalink
[CONJ-1180] improve DatabaseMeta.getExportedKeys() performances
Browse files Browse the repository at this point in the history
  • Loading branch information
rusher committed Jul 9, 2024
1 parent 45d8e6d commit 66a51df
Showing 1 changed file with 208 additions and 0 deletions.
208 changes: 208 additions & 0 deletions src/main/java/org/mariadb/jdbc/DatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<String[]> data = new ArrayList<>();
Statement stmt = connection.createStatement();

List<String> 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<String[], String> 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<String> 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<Identifier> foreignKeyCols = new ArrayList<>();
pos = parseIdentifierList(partChar, pos, foreignKeyCols);
pos = skipKeyword(partChar, pos, "REFERENCES");
Identifier pkTable = new Identifier();
pos = parseIdentifier(partChar, pos, pkTable);
List<Identifier> 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<String[], String> entry : externalInfo.entrySet()) {
boolean foundAll = true;
List<Identifier> 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(
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 66a51df

Please sign in to comment.