Skip to content

Commit c3362ec

Browse files
Merge pull request vitessio#2675 from HubSpot/jdbc_get_imported_keys
Support JDBC DatabaseMetaData getImportedKeys function
2 parents d8ea45d + 9bb39bd commit c3362ec

File tree

6 files changed

+939
-2
lines changed

6 files changed

+939
-2
lines changed

java/jdbc/src/main/java/com/flipkart/vitess/jdbc/VitessMySQLDatabaseMetadata.java

Lines changed: 192 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Collections;
1212
import java.util.HashMap;
1313
import java.util.Iterator;
14+
import java.util.List;
1415
import java.util.Locale;
1516
import java.util.Map;
1617
import java.util.SortedMap;
@@ -22,6 +23,7 @@
2223

2324
import com.flipkart.vitess.util.Constants;
2425
import com.flipkart.vitess.util.MysqlDefs;
26+
import com.google.common.annotations.VisibleForTesting;
2527
import com.youtube.vitess.proto.Query;
2628

2729
/**
@@ -898,8 +900,196 @@ public ResultSet getVersionColumns(String catalog, String schema, String table)
898900

899901
public ResultSet getImportedKeys(String catalog, String schema, String table)
900902
throws SQLException {
901-
throw new SQLFeatureNotSupportedException(
902-
Constants.SQLExceptionMessages.SQL_FEATURE_NOT_SUPPORTED);
903+
if (null == table) {
904+
throw new SQLException("Table Name Cannot be Null");
905+
}
906+
ResultSet resultSet = null;
907+
VitessStatement vitessStatement = new VitessStatement(this.connection);
908+
ArrayList<ArrayList<String>> rows = new ArrayList<>();
909+
try {
910+
resultSet = vitessStatement.executeQuery("SHOW CREATE TABLE " + this.quotedId + table + this.quotedId);
911+
while (resultSet.next()) {
912+
extractForeignKeyForTable(rows, resultSet.getString(2), catalog, table);
913+
}
914+
} finally {
915+
if (resultSet != null) {
916+
resultSet.close();
917+
}
918+
}
919+
String[] columnNames =
920+
new String[] {"PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CAT",
921+
"FKTABLE_SCHEM", "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE",
922+
"FK_NAME", "PK_NAME", "DEFERRABILITY"};
923+
Query.Type[] columnType =
924+
new Query.Type[] {Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR,
925+
Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.INT16,
926+
Query.Type.INT16, Query.Type.INT16, Query.Type.CHAR, Query.Type.CHAR, Query.Type.INT16};
927+
return new VitessResultSet(columnNames, columnType, rows, this.connection);
928+
}
929+
930+
@VisibleForTesting
931+
void extractForeignKeyForTable(List<ArrayList<String>> rows, String createTableString, String catalog, String table) throws SQLException {
932+
StringTokenizer lineTokenizer = new StringTokenizer(createTableString, "\n");
933+
934+
while (lineTokenizer.hasMoreTokens()) {
935+
String line = lineTokenizer.nextToken().trim();
936+
String constraintName = null;
937+
if (StringUtils.startsWithIgnoreCase(line, "CONSTRAINT")) {
938+
boolean usingBackTicks = true;
939+
int beginPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, this.quotedId, 0);
940+
if (beginPos == -1) {
941+
beginPos = line.indexOf("\"");
942+
usingBackTicks = false;
943+
}
944+
if (beginPos != -1) {
945+
int endPos;
946+
if (usingBackTicks) {
947+
endPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, this.quotedId, beginPos + 1);
948+
} else {
949+
endPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, "\"", beginPos + 1);
950+
}
951+
if (endPos != -1) {
952+
constraintName = line.substring(beginPos + 1, endPos);
953+
line = line.substring(endPos + 1, line.length()).trim();
954+
}
955+
}
956+
}
957+
958+
if (line.startsWith("FOREIGN KEY")) {
959+
if (line.endsWith(",")) {
960+
line = line.substring(0, line.length() - 1);
961+
}
962+
int indexOfFK = line.indexOf("FOREIGN KEY");
963+
String localColumnName = null;
964+
String referencedCatalogName = com.flipkart.vitess.util.StringUtils.quoteIdentifier(catalog, this.quotedId);
965+
String referencedTableName = null;
966+
String referencedColumnName = null;
967+
if (indexOfFK != -1) {
968+
int afterFk = indexOfFK + "FOREIGN KEY".length();
969+
970+
int indexOfRef = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(afterFk, line, "REFERENCES", this.quotedId, this.quotedId);
971+
if (indexOfRef != -1) {
972+
int indexOfParenOpen = line.indexOf('(', afterFk);
973+
int indexOfParenClose = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(indexOfParenOpen, line, ")", this.quotedId, this.quotedId);
974+
localColumnName = line.substring(indexOfParenOpen + 1, indexOfParenClose);
975+
976+
int afterRef = indexOfRef + "REFERENCES".length();
977+
int referencedColumnBegin = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(afterRef, line, "(", this.quotedId, this.quotedId);
978+
979+
if (referencedColumnBegin != -1) {
980+
referencedTableName = line.substring(afterRef, referencedColumnBegin);
981+
int referencedColumnEnd = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(referencedColumnBegin + 1, line, ")", this.quotedId, this.quotedId);
982+
if (referencedColumnEnd != -1) {
983+
referencedColumnName = line.substring(referencedColumnBegin + 1, referencedColumnEnd);
984+
}
985+
int indexOfCatalogSep = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(0, referencedTableName, ".", this.quotedId, this.quotedId);
986+
if (indexOfCatalogSep != -1) {
987+
referencedCatalogName = referencedTableName.substring(0, indexOfCatalogSep);
988+
referencedTableName = referencedTableName.substring(indexOfCatalogSep + 1);
989+
}
990+
}
991+
}
992+
}
993+
if (constraintName == null) {
994+
constraintName = "not_available";
995+
}
996+
List<String> localColumnsList = com.flipkart.vitess.util.StringUtils.split(localColumnName, ",", this.quotedId, this.quotedId);
997+
List<String> referColumnsList = com.flipkart.vitess.util.StringUtils.split(referencedColumnName, ",", this.quotedId, this.quotedId);
998+
if (localColumnsList.size() != referColumnsList.size()) {
999+
throw new SQLException("Mismatch columns list for foreign key local and reference columns");
1000+
}
1001+
// Report a separate row for each column in the foreign key. All values the same except the column name.
1002+
for (int i = 0; i < localColumnsList.size(); i++) {
1003+
String localColumn = localColumnsList.get(i);
1004+
String referColumn = referColumnsList.get(i);
1005+
ArrayList<String> row = new ArrayList<>(14);
1006+
row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referencedCatalogName, this.quotedId)); // PKTABLE_CAT
1007+
row.add(null); // PKTABLE_SCHEM
1008+
row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referencedTableName, this.quotedId)); // PKTABLE_NAME
1009+
row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referColumn, this.quotedId)); // PKCOLUMN_NAME
1010+
row.add(catalog); // FKTABLE_CAT
1011+
row.add(null); // FKTABLE_SCHEM
1012+
row.add(table); // FKTABLE_NAME
1013+
row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(localColumn, this.quotedId)); // FKCOLUMN_NAME
1014+
row.add(Integer.toString(i + 1)); // KEY_SEQ
1015+
int[] actions = getForeignKeyActions(line);
1016+
row.add(Integer.toString(actions[1])); // UPDATE_RULE
1017+
row.add(Integer.toString(actions[0])); // DELETE_RULE
1018+
row.add(constraintName); // FK_NAME
1019+
row.add(null); // PK_NAME
1020+
row.add(Integer.toString(java.sql.DatabaseMetaData.importedKeyNotDeferrable)); // DEFERRABILITY
1021+
rows.add(row);
1022+
}
1023+
}
1024+
}
1025+
}
1026+
1027+
1028+
/**
1029+
* Parses the constraint to see what actions are taking for update and delete, such as cascade.
1030+
* @param constraint
1031+
* the constraint to parse
1032+
* @return the code from {@link DatabaseMetaData} corresponding to the foreign actions for the constraint
1033+
*/
1034+
private int[] getForeignKeyActions(String constraint) {
1035+
int[] actions = new int[] { java.sql.DatabaseMetaData.importedKeyNoAction, java.sql.DatabaseMetaData.importedKeyNoAction };
1036+
int lastParenIndex = constraint.lastIndexOf(")");
1037+
if (lastParenIndex != (constraint.length() - 1)) {
1038+
String cascadeOptions = constraint.substring(lastParenIndex + 1).trim().toUpperCase(Locale.ENGLISH);
1039+
actions[0] = getCascadeDeleteOption(cascadeOptions);
1040+
actions[1] = getCascadeUpdateOption(cascadeOptions);
1041+
}
1042+
return actions;
1043+
}
1044+
1045+
/**
1046+
* Parses the cascade option string and returns the DBMD constant that
1047+
* represents it (for deletes)
1048+
*
1049+
* @param cascadeOptions
1050+
* the comment from 'SHOW TABLE STATUS'
1051+
* @return the DBMD constant that represents the cascade option
1052+
*/
1053+
private int getCascadeDeleteOption(String cascadeOptions) {
1054+
int onDeletePos = cascadeOptions.indexOf("ON DELETE");
1055+
if (onDeletePos != -1) {
1056+
String deleteOptions = cascadeOptions.substring(onDeletePos, cascadeOptions.length());
1057+
if (deleteOptions.startsWith("ON DELETE CASCADE")) {
1058+
return java.sql.DatabaseMetaData.importedKeyCascade;
1059+
} else if (deleteOptions.startsWith("ON DELETE SET NULL")) {
1060+
return java.sql.DatabaseMetaData.importedKeySetNull;
1061+
} else if (deleteOptions.startsWith("ON DELETE RESTRICT")) {
1062+
return java.sql.DatabaseMetaData.importedKeyRestrict;
1063+
} else if (deleteOptions.startsWith("ON DELETE NO ACTION")) {
1064+
return java.sql.DatabaseMetaData.importedKeyNoAction;
1065+
}
1066+
}
1067+
return java.sql.DatabaseMetaData.importedKeyNoAction;
1068+
}
1069+
1070+
/**
1071+
* Parses the cascade option string and returns the DBMD constant that
1072+
* represents it (for Updates)
1073+
*
1074+
* @param cascadeOptions
1075+
* the comment from 'SHOW TABLE STATUS'
1076+
* @return the DBMD constant that represents the cascade option
1077+
*/
1078+
private int getCascadeUpdateOption(String cascadeOptions) {
1079+
int onUpdatePos = cascadeOptions.indexOf("ON UPDATE");
1080+
if (onUpdatePos != -1) {
1081+
String updateOptions = cascadeOptions.substring(onUpdatePos, cascadeOptions.length());
1082+
if (updateOptions.startsWith("ON UPDATE CASCADE")) {
1083+
return java.sql.DatabaseMetaData.importedKeyCascade;
1084+
} else if (updateOptions.startsWith("ON UPDATE SET NULL")) {
1085+
return java.sql.DatabaseMetaData.importedKeySetNull;
1086+
} else if (updateOptions.startsWith("ON UPDATE RESTRICT")) {
1087+
return java.sql.DatabaseMetaData.importedKeyRestrict;
1088+
} else if (updateOptions.startsWith("ON UPDATE NO ACTION")) {
1089+
return java.sql.DatabaseMetaData.importedKeyNoAction;
1090+
}
1091+
}
1092+
return java.sql.DatabaseMetaData.importedKeyNoAction;
9031093
}
9041094

9051095
public ResultSet getExportedKeys(String catalog, String schema, String table)

0 commit comments

Comments
 (0)