Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,32 @@ public static TableMetadata inspect(Connection conn, String schema, String table
TableMetadata meta = new TableMetadata(schema, table);
DatabaseMetaData md = conn.getMetaData();

// Case-tolerant identifier filter resolution. JDBC metadata lookups match
// schema/table filters EXACTLY (no case folding). Unquoted identifiers are
// stored UPPERCASE by H2 v2 and other SQL-standard-compliant databases but
// natively lowercase by postgres; a filter in the wrong case silently yields
// an empty metadata set. Probe as-given first -- when it already matches
// (postgres and any correctly-cased call) behavior and ordering are
// unchanged -- then retry UPPER, then lower. The display names recorded in
// `meta` intentionally keep the as-given values.
String fSchema = schema;
String fTable = table;
if (table != null && !hasColumns(md, schema, table)) {
String[][] candidates = {
{schema == null ? null : schema.toUpperCase(Locale.ROOT), table.toUpperCase(Locale.ROOT)},
{schema == null ? null : schema.toLowerCase(Locale.ROOT), table.toLowerCase(Locale.ROOT)},
};
for (String[] cand : candidates) {
if (hasColumns(md, cand[0], cand[1])) {
fSchema = cand[0];
fTable = cand[1];
break;
}
}
}

// columns
try (ResultSet cols = md.getColumns(null, schema, table, null)) {
try (ResultSet cols = md.getColumns(null, fSchema, fTable, null)) {
while (cols.next()) {
String name = cols.getString("COLUMN_NAME");
String type = cols.getString("TYPE_NAME");
Expand All @@ -68,12 +92,12 @@ public static TableMetadata inspect(Connection conn, String schema, String table
}

// primary keys
try (ResultSet pk = md.getPrimaryKeys(null, schema, table)) {
try (ResultSet pk = md.getPrimaryKeys(null, fSchema, fTable)) {
while (pk.next()) meta.primaryKeys.add(pk.getString("COLUMN_NAME"));
}

// indexes
try (ResultSet ix = md.getIndexInfo(null, schema, table, false, false)) {
try (ResultSet ix = md.getIndexInfo(null, fSchema, fTable, false, false)) {
while (ix.next()) {
String iname = ix.getString("INDEX_NAME");
String col = ix.getString("COLUMN_NAME");
Expand All @@ -85,7 +109,7 @@ public static TableMetadata inspect(Connection conn, String schema, String table
}

// foreign keys
try (ResultSet fk = md.getImportedKeys(null, schema, table)) {
try (ResultSet fk = md.getImportedKeys(null, fSchema, fTable)) {
while (fk.next()) {
String pkTable = fk.getString("PKTABLE_NAME");
String pkCol = fk.getString("PKCOLUMN_NAME");
Expand All @@ -95,7 +119,7 @@ public static TableMetadata inspect(Connection conn, String schema, String table
}

// table remarks (best-effort via getTables)
try (ResultSet t = md.getTables(null, schema, table, null)) {
try (ResultSet t = md.getTables(null, fSchema, fTable, null)) {
if (t.next()) {
meta.tableRemarks = t.getString("REMARKS");
}
Expand All @@ -108,12 +132,12 @@ public static TableMetadata inspect(Connection conn, String schema, String table
if (schema != null) {
qc = "SELECT constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_schema = ? AND table_name = ?";
ps = conn.prepareStatement(qc);
ps.setString(1, schema);
ps.setString(2, table);
ps.setString(1, fSchema);
ps.setString(2, fTable);
} else {
qc = "SELECT constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_name = ?";
ps = conn.prepareStatement(qc);
ps.setString(1, table);
ps.setString(1, fTable);
}
try (ResultSet cr = ps.executeQuery()) {
while (cr.next()) meta.constraints.add(cr.getString("constraint_name") + ": " + cr.getString("constraint_type"));
Expand All @@ -127,12 +151,12 @@ public static TableMetadata inspect(Connection conn, String schema, String table
if (schema != null) {
qc = "SELECT cc.constraint_name, cc.check_clause FROM information_schema.check_constraints cc JOIN information_schema.table_constraints tc ON cc.constraint_name = tc.constraint_name WHERE tc.table_schema = ? AND tc.table_name = ?";
ps = conn.prepareStatement(qc);
ps.setString(1, schema);
ps.setString(2, table);
ps.setString(1, fSchema);
ps.setString(2, fTable);
} else {
qc = "SELECT cc.constraint_name, cc.check_clause FROM information_schema.check_constraints cc JOIN information_schema.table_constraints tc ON cc.constraint_name = tc.constraint_name WHERE tc.table_name = ?";
ps = conn.prepareStatement(qc);
ps.setString(1, table);
ps.setString(1, fTable);
}
try (ResultSet cr = ps.executeQuery()) {
while (cr.next()) meta.checks.add(cr.getString("constraint_name") + ": " + cr.getString("check_clause"));
Expand All @@ -144,10 +168,10 @@ public static TableMetadata inspect(Connection conn, String schema, String table
"SELECT column_name, domain_name, udt_name FROM information_schema.columns WHERE table_schema = ? AND table_name = ?" :
"SELECT column_name, domain_name, udt_name FROM information_schema.columns WHERE table_name = ?")) {
if (schema != null) {
ps.setString(1, schema);
ps.setString(2, table);
ps.setString(1, fSchema);
ps.setString(2, fTable);
} else {
ps.setString(1, table);
ps.setString(1, fTable);
}
try (ResultSet cr = ps.executeQuery()) {
while (cr.next()) {
Expand All @@ -163,4 +187,11 @@ public static TableMetadata inspect(Connection conn, String schema, String table

return meta;
}

/** True when the JDBC metadata column lookup for this filter pair yields at least one row. */
private static boolean hasColumns(DatabaseMetaData md, String schema, String table) throws SQLException {
try (ResultSet cols = md.getColumns(null, schema, table, null)) {
return cols.next();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.github.spencerpark.ijava.magics;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

import static org.junit.Assert.*;

/**
* Regression tests for case-tolerant metadata filter resolution
* (H2 v2 stores unquoted identifiers UPPERCASE; the metadata filters
* used to be passed verbatim, silently yielding empty metadata).
*/
public class DBMetadataInspectorCaseTest {

private static Connection h2WithEmployees() throws Exception {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:casetest" + System.nanoTime()
+ ";DB_CLOSE_DELAY=-1");
try (Statement st = conn.createStatement()) {
st.execute("CREATE TABLE employees (id INT PRIMARY KEY, name VARCHAR(64))");
st.execute("INSERT INTO employees VALUES (1, 'alice')");
}
return conn;
}

@Test
public void lowercaseFilterResolvesToUpperStoredTable() throws Exception {
try (Connection conn = h2WithEmployees()) {
DBMetadataInspector.TableMetadata meta = DBMetadataInspector.inspect(conn, null, "employees");
assertEquals("wrong-case filter must fall back to the UPPER-stored table",
2, meta.columns.size());
assertFalse(meta.primaryKeys.isEmpty());
// display name stays as given
assertEquals("employees", meta.table);
}
}

@Test
public void asGivenMatchWinsAndIsUnchanged() throws Exception {
try (Connection conn = h2WithEmployees()) {
DBMetadataInspector.TableMetadata meta = DBMetadataInspector.inspect(conn, null, "EMPLOYEES");
assertEquals(2, meta.columns.size());
assertEquals("ID", meta.columns.get(0).name); // ordinal order preserved
assertEquals("EMPLOYEES", meta.table);
}
}

@Test
public void missingTableStaysEmpty() throws Exception {
try (Connection conn = h2WithEmployees()) {
DBMetadataInspector.TableMetadata meta = DBMetadataInspector.inspect(conn, null, "nosuchtable");
assertTrue(meta.columns.isEmpty());
}
}
}
Loading