Skip to content

Commit 96ff0ab

Browse files
staticlibslukaseder
andcommitted
Support foreign keys introspection (1.4)
This is a backport of the PR duckdb#489 to `v1.4-andium` stable branch. This PR implements the following methods on the `DatabaseMetadata`: - `getImportedKeys` - `getExportedKeys` - `getCrossReference` Testing: new tests added for foreign keys methods. Fixes: duckdb#149 Co-authored-by: Lukas Eder <lukas.eder@datageekery.com>
1 parent 7dd9ae6 commit 96ff0ab

3 files changed

Lines changed: 1160 additions & 949 deletions

File tree

src/main/java/org/duckdb/DuckDBDatabaseMetaData.java

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -950,21 +950,158 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr
950950
return ps.executeQuery();
951951
}
952952

953+
private void appendAndQual(StringBuilder sb, boolean needed) {
954+
if (needed) {
955+
sb.append(" AND ");
956+
} else {
957+
sb.append(" ");
958+
}
959+
}
960+
953961
@Override
954962
public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
955-
throw new SQLFeatureNotSupportedException("getImportedKeys");
963+
return getCrossReference(null, null, null, catalog, schema, table);
956964
}
957965

958966
@Override
959967
public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
960-
throw new SQLFeatureNotSupportedException("getExportedKeys");
968+
return getCrossReference(catalog, schema, table, null, null, null);
961969
}
962970

963971
@Override
964972
public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable,
965973
String foreignCatalog, String foreignSchema, String foreignTable)
966974
throws SQLException {
967-
throw new SQLFeatureNotSupportedException("getCrossReference");
975+
StringBuilder sb = new StringBuilder(QUERY_SB_DEFAULT_CAPACITY);
976+
sb.append("SELECT").append(lineSeparator());
977+
sb.append(" pk_tc.table_catalog AS PKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
978+
sb.append(" pk_tc.table_schema AS PKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
979+
sb.append(" pk_tc.table_name AS PKTABLE_NAME").append(TRAILING_COMMA).append(lineSeparator());
980+
sb.append(" pk_kcu.column_name AS PKCOLUMN_NAME").append(TRAILING_COMMA).append(lineSeparator());
981+
sb.append(" fk_tc.table_catalog AS FKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
982+
sb.append(" fk_tc.table_schema AS FKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
983+
sb.append(" fk_tc.table_name AS FKTABLE_NAME").append(TRAILING_COMMA).append(lineSeparator());
984+
sb.append(" fk_kcu.column_name AS FKCOLUMN_NAME").append(TRAILING_COMMA).append(lineSeparator());
985+
sb.append(" fk_kcu.ordinal_position AS KEY_SEQ").append(TRAILING_COMMA).append(lineSeparator());
986+
sb.append(" CASE rc.update_rule ").append(lineSeparator());
987+
sb.append(" WHEN 'CASCADE' THEN 0").append(lineSeparator());
988+
sb.append(" WHEN 'RESTRICT' THEN 1").append(lineSeparator());
989+
sb.append(" WHEN 'SET NULL' THEN 2").append(lineSeparator());
990+
sb.append(" WHEN 'SET DEFAULT' THEN 4").append(lineSeparator());
991+
sb.append(" ELSE 3").append(lineSeparator());
992+
sb.append(" END AS UPDATE_RULE,").append(lineSeparator());
993+
sb.append(" CASE rc.delete_rule").append(lineSeparator());
994+
sb.append(" WHEN 'CASCADE' THEN 0").append(lineSeparator());
995+
sb.append(" WHEN 'RESTRICT' THEN 1").append(lineSeparator());
996+
sb.append(" WHEN 'SET NULL' THEN 2").append(lineSeparator());
997+
sb.append(" WHEN 'SET DEFAULT' THEN 4").append(lineSeparator());
998+
sb.append(" ELSE 3").append(lineSeparator());
999+
sb.append(" END AS DELETE_RULE,").append(lineSeparator());
1000+
sb.append(" rc.constraint_name AS FK_NAME,").append(lineSeparator());
1001+
sb.append(" rc.unique_constraint_name AS PK_NAME,").append(lineSeparator());
1002+
sb.append(" CASE ").append(lineSeparator());
1003+
sb.append(" WHEN fk_tc.is_deferrable = 'YES' AND fk_tc.initially_deferred = 'YES' THEN 5")
1004+
.append(lineSeparator());
1005+
sb.append(" WHEN fk_tc.is_deferrable = 'YES' AND fk_tc.initially_deferred = 'NO' THEN 6")
1006+
.append(lineSeparator());
1007+
sb.append(" ELSE 7").append(lineSeparator());
1008+
sb.append(" END AS DEFERRABILITY").append(lineSeparator());
1009+
sb.append("FROM information_schema.referential_constraints rc").append(lineSeparator());
1010+
sb.append(" JOIN information_schema.table_constraints fk_tc").append(lineSeparator());
1011+
sb.append(" ON fk_tc.constraint_catalog = rc.constraint_catalog").append(lineSeparator());
1012+
sb.append(" AND fk_tc.constraint_schema = rc.constraint_schema").append(lineSeparator());
1013+
sb.append(" AND fk_tc.constraint_name = rc.constraint_name").append(lineSeparator());
1014+
sb.append(" JOIN information_schema.key_column_usage fk_kcu").append(lineSeparator());
1015+
sb.append(" ON fk_kcu.constraint_catalog = rc.constraint_catalog").append(lineSeparator());
1016+
sb.append(" AND fk_kcu.constraint_schema = rc.constraint_schema").append(lineSeparator());
1017+
sb.append(" AND fk_kcu.constraint_name = rc.constraint_name").append(lineSeparator());
1018+
sb.append(" JOIN information_schema.table_constraints pk_tc").append(lineSeparator());
1019+
sb.append(" ON pk_tc.constraint_catalog = rc.unique_constraint_catalog").append(lineSeparator());
1020+
sb.append(" AND pk_tc.constraint_schema = rc.unique_constraint_schema").append(lineSeparator());
1021+
sb.append(" AND pk_tc.constraint_name = rc.unique_constraint_name").append(lineSeparator());
1022+
sb.append(" JOIN information_schema.key_column_usage pk_kcu").append(lineSeparator());
1023+
sb.append(" ON pk_kcu.constraint_catalog = pk_tc.constraint_catalog").append(lineSeparator());
1024+
sb.append(" AND pk_kcu.constraint_schema = pk_tc.constraint_schema").append(lineSeparator());
1025+
sb.append(" AND pk_kcu.constraint_name = pk_tc.constraint_name").append(lineSeparator());
1026+
sb.append(" AND pk_kcu.ordinal_position = fk_kcu.ordinal_position").append(lineSeparator());
1027+
sb.append(" -- AND pk_kcu.ordinal_position = fk_kcu.position_in_unique_constraint").append(lineSeparator());
1028+
if (null != parentCatalog || null != parentSchema || null != parentTable || null != foreignCatalog ||
1029+
null != foreignSchema || null != foreignTable) {
1030+
sb.append("WHERE");
1031+
}
1032+
boolean andQualNeeded = false;
1033+
if (null != parentCatalog) {
1034+
appendAndQual(sb, andQualNeeded);
1035+
sb.append("pk_tc.table_catalog = ?").append(lineSeparator());
1036+
andQualNeeded = true;
1037+
}
1038+
if (null != parentSchema) {
1039+
appendAndQual(sb, andQualNeeded);
1040+
sb.append("pk_tc.table_schema = ?").append(lineSeparator());
1041+
andQualNeeded = true;
1042+
}
1043+
if (null != parentTable) {
1044+
appendAndQual(sb, andQualNeeded);
1045+
sb.append("pk_tc.table_name = ?").append(lineSeparator());
1046+
andQualNeeded = true;
1047+
}
1048+
if (null != foreignCatalog) {
1049+
appendAndQual(sb, andQualNeeded);
1050+
sb.append("fk_tc.table_catalog = ?").append(lineSeparator());
1051+
andQualNeeded = true;
1052+
}
1053+
if (null != foreignSchema) {
1054+
appendAndQual(sb, andQualNeeded);
1055+
sb.append("fk_tc.table_schema = ?").append(lineSeparator());
1056+
andQualNeeded = true;
1057+
}
1058+
if (null != foreignTable) {
1059+
appendAndQual(sb, andQualNeeded);
1060+
sb.append("fk_tc.table_name = ?").append(lineSeparator());
1061+
andQualNeeded = true;
1062+
}
1063+
sb.append("ORDER BY ").append(lineSeparator());
1064+
if (null != foreignTable) { // imported keys
1065+
sb.append(" PKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
1066+
sb.append(" PKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
1067+
sb.append(" PKTABLE_NAME").append(TRAILING_COMMA).append(lineSeparator());
1068+
sb.append(" KEY_SEQ").append(TRAILING_COMMA).append(lineSeparator());
1069+
sb.append(" FKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
1070+
sb.append(" FKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
1071+
sb.append(" FKTABLE_NAME");
1072+
} else { // exported keys or cross-reference
1073+
sb.append(" FKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
1074+
sb.append(" FKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
1075+
sb.append(" FKTABLE_NAME").append(TRAILING_COMMA).append(lineSeparator());
1076+
sb.append(" KEY_SEQ").append(TRAILING_COMMA).append(lineSeparator());
1077+
sb.append(" PKTABLE_CAT").append(TRAILING_COMMA).append(lineSeparator());
1078+
sb.append(" PKTABLE_SCHEM").append(TRAILING_COMMA).append(lineSeparator());
1079+
sb.append(" PKTABLE_NAME");
1080+
}
1081+
1082+
PreparedStatement ps = conn.prepareStatement(sb.toString());
1083+
int paramIdx = 1;
1084+
if (null != parentCatalog) {
1085+
ps.setString(paramIdx++, parentCatalog);
1086+
}
1087+
if (null != parentSchema) {
1088+
ps.setString(paramIdx++, parentSchema);
1089+
}
1090+
if (null != parentTable) {
1091+
ps.setString(paramIdx++, parentTable);
1092+
}
1093+
if (null != foreignCatalog) {
1094+
ps.setString(paramIdx++, foreignCatalog);
1095+
}
1096+
if (null != foreignSchema) {
1097+
ps.setString(paramIdx++, foreignSchema);
1098+
}
1099+
if (null != foreignTable) {
1100+
ps.setString(paramIdx++, foreignTable);
1101+
}
1102+
1103+
ps.closeOnCompletion();
1104+
return ps.executeQuery();
9681105
}
9691106

9701107
@Override

0 commit comments

Comments
 (0)