fix(jdbc): avoid privileged Kingbase metadata query

This commit is contained in:
Spencer.Chang 2026-07-20 16:54:51 +08:00 committed by GitHub
parent 039ec43440
commit d69f9d3cde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 502 additions and 25 deletions

View File

@ -869,8 +869,12 @@ public final class KingbaseAgent extends PostgresLikeAgent {
.append("FROM sys_catalog.sys_class c ")
.append("JOIN sys_catalog.sys_namespace n ON ").append(KINGBASE_NAMESPACE_OID).append(" = ").append(KINGBASE_REL_NAMESPACE).append(' ')
.append("LEFT JOIN sys_catalog.sys_description d ON CAST(d.objoid AS varchar(64)) = ").append(KINGBASE_REL_OID).append(" AND d.objsubid = 0 ")
.append("WHERE ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(sqlString(schema));
appendRegularTablePredicate(sql);
.append("WHERE ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(sqlString(schema))
.append(" AND (EXISTS (SELECT 1 FROM sys_catalog.sys_tables t ")
.append("WHERE CAST(t.schemaname AS varchar(256)) = ").append(KINGBASE_SCHEMA_NAME)
.append(" AND CAST(t.tablename AS varchar(256)) = ").append(KINGBASE_REL_NAME).append(')')
.append(" OR EXISTS (SELECT 1 FROM sys_catalog.sys_foreign_table ft ")
.append("WHERE CAST(ft.ftrelid AS varchar(64)) = ").append(KINGBASE_REL_OID).append("))");
MetadataSqlSupport.appendNameFilter(sql, args, KINGBASE_REL_NAME, constraints);
return sql.toString();
}
@ -919,19 +923,6 @@ public final class KingbaseAgent extends PostgresLikeAgent {
return sql.toString();
}
private static void appendRegularTablePredicate(StringBuilder sql) {
sql.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_rewrite r ")
.append("WHERE CAST(r.ev_class AS varchar(64)) = ").append(KINGBASE_REL_OID)
.append(" AND CAST(r.rulename AS varchar(256)) = '_RETURN')")
.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_index ix ")
.append("WHERE CAST(ix.indexrelid AS varchar(64)) = ").append(KINGBASE_REL_OID).append(')')
.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_attribute sa1 ")
.append("JOIN sys_catalog.sys_attribute sa2 ON CAST(sa2.attrelid AS varchar(64)) = CAST(sa1.attrelid AS varchar(64)) ")
.append("WHERE CAST(sa1.attrelid AS varchar(64)) = ").append(KINGBASE_REL_OID)
.append(" AND CAST(sa1.attname AS varchar(256)) = 'last_value'")
.append(" AND CAST(sa2.attname AS varchar(256)) = 'log_cnt')");
}
private static void appendRelationVisibilityPredicate(StringBuilder sql) {
sql.append(" AND (SYS_HAS_ROLE(").append(KINGBASE_REL_OWNER).append(", 'USAGE')")
.append(" OR HAS_TABLE_PRIVILEGE(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')")

View File

@ -167,6 +167,30 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertEquals("RESET search_path", agent.resetSchemaSQL());
}
@Test
void postgresCompatModePreservesMaterializedViews() throws Exception {
List<String> sql = new ArrayList<>();
KingbaseAgent agent = new KingbaseAgent();
Connection connection = postgresCatalogConnection(sql, resultSet(
new String[]{"table_name", "table_type", "table_comment"},
new Object[][]{{"orders", "TABLE", null}, {"order_summary", "MATERIALIZED VIEW", "cached orders"}}
));
Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class);
afterConnect.setAccessible(true);
afterConnect.invoke(agent, new ConnectParams(), connection);
TestSupport.setPrivateConnection(agent, connection);
List<TableInfo> tables = agent.listTables("public");
Assertions.assertEquals(2, tables.size());
Assertions.assertEquals("MATERIALIZED VIEW", tables.get(1).getTable_type());
Assertions.assertEquals("SELECT 1 FROM sys_catalog.sys_namespace WHERE 1 = 0", sql.get(0));
Assertions.assertEquals("SELECT 1 FROM pg_catalog.pg_namespace WHERE 1 = 0", sql.get(1));
Assertions.assertTrue(sql.get(2).contains("FROM pg_catalog.pg_class c"), sql.get(2));
Assertions.assertTrue(sql.get(2).contains("c.relkind IN ('r','p','v','m','f')"), sql.get(2));
}
@Test
void detectsMysqlCompatModeFromServerDatabaseMode() throws Exception {
List<String> sql = new ArrayList<>();
@ -331,14 +355,32 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertEquals("TABLE", tables.get(0).getTable_type());
Assertions.assertEquals("app_view", tables.get(1).getName());
Assertions.assertEquals("VIEW", tables.get(1).getTable_type());
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_rewrite"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_index"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class c"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_tables t"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_foreign_table ft"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_views"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_matviews"), sql.get(0));
Assertions.assertFalse(sql.get(0).contains("relkind"), sql.get(0));
}
@Test
void regularTableDiscoveryExcludesCompositeTypesWithPositiveTableCatalog() {
List<String> sql = new ArrayList<>();
KingbaseAgent agent = new KingbaseAgent();
TestSupport.setPrivateConnection(agent, compositeAwareTableConnection(sql));
List<TableInfo> tables = agent.listTables("public", new MetadataListConstraints(null, null, null, List.of("TABLE")));
Assertions.assertEquals(1, tables.size());
Assertions.assertEquals("orders", tables.get(0).getName());
Assertions.assertFalse(tables.stream().anyMatch(table -> "address_type".equals(table.getName())));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_tables t"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_foreign_table ft"), sql.get(0));
Assertions.assertFalse(sql.get(0).contains("information_schema.tables"), sql.get(0));
Assertions.assertFalse(sql.get(0).contains("sys_rewrite"), sql.get(0));
Assertions.assertFalse(sql.get(0).contains("sys_index"), sql.get(0));
}
@Test
void regularListObjectsIncludesKingbaseViewsProceduresAndFunctions() {
List<String> sql = new ArrayList<>();
@ -431,7 +473,8 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
agent.listTables("public", new MetadataListConstraints("ord", 30, 60, List.of("TABLE", "VIEW")));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class c"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_tables t"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_views"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("UNION ALL"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("UPPER(CAST(c.relname AS varchar(256))) LIKE ? ESCAPE '\\\\'"), sql.get(0));
@ -789,6 +832,33 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
});
}
private static Connection compositeAwareTableConnection(List<String> sql) {
return proxy(Connection.class, (method, args) -> {
if ("prepareStatement".equals(method.getName())) {
String preparedSql = String.valueOf(args[0]);
sql.add(preparedSql);
boolean positivelySelectsTables = preparedSql.contains("FROM sys_catalog.sys_tables t")
&& preparedSql.contains("FROM sys_catalog.sys_foreign_table ft");
Object[][] rows = positivelySelectsTables
? new Object[][]{{"orders", "TABLE", null}}
: new Object[][]{{"orders", "TABLE", null}, {"address_type", "TABLE", null}};
return proxy(PreparedStatement.class, (statementMethod, statementArgs) -> {
if ("executeQuery".equals(statementMethod.getName())) {
return resultSet(new String[]{"table_name", "table_type", "table_comment"}, rows);
}
if ("setString".equals(statementMethod.getName()) || "close".equals(statementMethod.getName())) {
return null;
}
return defaultValue(statementMethod.getReturnType());
});
}
if ("isClosed".equals(method.getName())) {
return false;
}
return defaultValue(method.getReturnType());
});
}
private static Connection preparedConnectionWithFailure(List<String> sql, String failingSqlFragment, ResultSet fallback) {
return preparedConnectionWithFailures(sql, List.of(failingSqlFragment), fallback);
}

View File

@ -1319,6 +1319,17 @@ public final class DbxJdbcPlugin {
if (usePrestoInformationSchemaTables(connection)) {
return prestoListTables(conn, database, schema, filter, limit, offset, objectTypes);
}
if (isKingbaseUrl(optionalText(connection, "connection_string"))) {
return filterMetadataNodes(
(ArrayNode) kingbaseListTables(conn, schema, false),
filter,
limit,
offset,
objectTypes,
"table_type",
true
);
}
DatabaseMetaData meta = conn.getMetaData();
String[] types = constrainedJdbcTableTypes(jdbcTableTypes(meta), objectTypes);
if (types.length == 0) {
@ -1358,16 +1369,22 @@ public final class DbxJdbcPlugin {
if (usePrestoInformationSchemaTables(connection)) {
return prestoListObjects(conn, database, schema, filter, limit, offset, objectTypes);
}
boolean kingbase = isKingbaseUrl(optionalText(connection, "connection_string"));
if (kingbase) {
result.addAll((ArrayNode) kingbaseListTables(conn, schema, true));
}
DatabaseMetaData meta = conn.getMetaData();
JdbcDriverQuirks quirks = driverQuirks(connection);
String catalog = metadataCatalog(database, quirks);
String schemaPattern = resolveSchemaPattern(meta, database, schema, quirks);
String[] tableTypes = constrainedJdbcTableTypes(jdbcTableTypes(meta), objectTypes);
if (tableTypes.length > 0) {
appendTableObjects(result, meta, catalog, schemaPattern, schema, tableTypes);
if (result.isEmpty() && catalog != null) {
appendTableObjects(result, meta, null, schemaPattern, schema, tableTypes);
if (!kingbase) {
String[] tableTypes = constrainedJdbcTableTypes(jdbcTableTypes(meta), objectTypes);
if (tableTypes.length > 0) {
appendTableObjects(result, meta, catalog, schemaPattern, schema, tableTypes);
if (result.isEmpty() && catalog != null) {
appendTableObjects(result, meta, null, schemaPattern, schema, tableTypes);
}
}
}
@ -1880,9 +1897,192 @@ public final class DbxJdbcPlugin {
};
}
private static JsonNode kingbaseListTables(Connection conn, String schema, boolean objectNodes) throws SQLException {
ArrayNode result = MAPPER.createArrayNode();
String effectiveSchema = kingbaseEffectiveSchema(conn, schema);
KingbaseTableCatalogMode catalogMode = kingbaseTableCatalogMode(conn);
String sql = switch (catalogMode) {
case SYS_CATALOG -> kingbaseCastSafeTablesSql();
case POSTGRES_CATALOG -> kingbasePostgresTablesSql();
case INFORMATION_SCHEMA -> kingbaseCompatibilityTablesSql();
};
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, effectiveSchema);
if (catalogMode == KingbaseTableCatalogMode.SYS_CATALOG) {
ps.setString(2, effectiveSchema);
ps.setString(3, effectiveSchema);
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String tableName = rs.getString("table_name");
String tableType = normalizeInformationSchemaTableType(rs.getString("table_type"));
ObjectNode item = MAPPER.createObjectNode();
item.put("name", tableName);
if (objectNodes) {
item.put("object_type", tableType);
item.put("schema", effectiveSchema);
} else {
item.put("table_type", tableType);
}
putNullable(item, "comment", rs.getString("remarks"));
result.add(item);
}
}
}
return result;
}
private enum KingbaseTableCatalogMode {
SYS_CATALOG,
POSTGRES_CATALOG,
INFORMATION_SCHEMA
}
private static KingbaseTableCatalogMode kingbaseTableCatalogMode(Connection conn) {
if (!kingbaseCatalogExists(conn, "sys_catalog.sys_namespace")) {
return kingbaseCatalogExists(conn, "pg_catalog.pg_namespace")
? KingbaseTableCatalogMode.POSTGRES_CATALOG
: KingbaseTableCatalogMode.INFORMATION_SCHEMA;
}
return kingbaseMysqlCompatibilityMode(conn)
? KingbaseTableCatalogMode.INFORMATION_SCHEMA
: KingbaseTableCatalogMode.SYS_CATALOG;
}
private static boolean kingbaseMysqlCompatibilityMode(Connection conn) {
try (Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery(
"SELECT setting FROM sys_catalog.sys_settings WHERE LOWER(name) = 'database_mode'"
)) {
if (rs.next()) {
return "mysql".equalsIgnoreCase(rs.getString(1));
}
} catch (Exception ignored) {
// Older Kingbase versions do not expose database_mode.
}
try (Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery(
"SELECT 1 FROM sys_catalog.sys_settings WHERE LOWER(name) = 'sql_mode'"
)) {
return rs.next();
} catch (Exception ignored) {
return false;
}
}
private static boolean kingbaseCatalogExists(Connection conn, String catalog) {
try (Statement statement = conn.createStatement();
ResultSet ignored = statement.executeQuery("SELECT 1 FROM " + catalog + " WHERE 1 = 0")) {
return true;
} catch (Exception ignored) {
return false;
}
}
private static String kingbaseCompatibilityTablesSql() {
return """
SELECT CAST(table_name AS varchar(256)) AS table_name,
CASE UPPER(CAST(table_type AS varchar(64)))
WHEN 'VIEW' THEN 'VIEW'
WHEN 'MATERIALIZED VIEW' THEN 'MATERIALIZED_VIEW'
ELSE 'TABLE'
END AS table_type,
NULL AS remarks
FROM information_schema.tables
WHERE CAST(table_schema AS varchar(256)) = ?
AND UPPER(CAST(table_type AS varchar(64))) IN ('BASE TABLE', 'TABLE', 'VIEW', 'MATERIALIZED VIEW')
ORDER BY CAST(table_name AS varchar(256))
""";
}
private static String kingbasePostgresTablesSql() {
return """
SELECT CAST(c.relname AS varchar(256)) AS table_name,
CASE c.relkind
WHEN 'v' THEN 'VIEW'
WHEN 'm' THEN 'MATERIALIZED_VIEW'
WHEN 'f' THEN 'FOREIGN TABLE'
ELSE 'TABLE'
END AS table_type,
CAST(obj_description(c.oid) AS varchar(4000)) AS remarks
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = ?
AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
ORDER BY c.relname
""";
}
private static String kingbaseCastSafeTablesSql() {
return """
SELECT table_name, table_type, remarks
FROM (
SELECT CAST(c.relname AS varchar(256)) AS table_name,
'TABLE' AS table_type,
CAST(d.description AS varchar(4000)) AS remarks
FROM sys_catalog.sys_class c
JOIN sys_catalog.sys_namespace n
ON CAST(n.oid AS varchar(64)) = CAST(c.relnamespace AS varchar(64))
LEFT JOIN sys_catalog.sys_description d
ON CAST(d.objoid AS varchar(64)) = CAST(c.oid AS varchar(64)) AND d.objsubid = 0
WHERE CAST(n.nspname AS varchar(256)) = ?
AND (
EXISTS (
SELECT 1 FROM sys_catalog.sys_tables t
WHERE CAST(t.schemaname AS varchar(256)) = CAST(n.nspname AS varchar(256))
AND CAST(t.tablename AS varchar(256)) = CAST(c.relname AS varchar(256))
)
OR EXISTS (
SELECT 1 FROM sys_catalog.sys_foreign_table ft
WHERE CAST(ft.ftrelid AS varchar(64)) = CAST(c.oid AS varchar(64))
)
)
UNION ALL
SELECT CAST(v.viewname AS varchar(256)) AS table_name,
'VIEW' AS table_type,
CAST(d.description AS varchar(4000)) AS remarks
FROM sys_catalog.sys_views v
JOIN sys_catalog.sys_namespace n
ON CAST(n.nspname AS varchar(256)) = CAST(v.schemaname AS varchar(256))
JOIN sys_catalog.sys_class c
ON CAST(c.relnamespace AS varchar(64)) = CAST(n.oid AS varchar(64))
AND CAST(c.relname AS varchar(256)) = CAST(v.viewname AS varchar(256))
LEFT JOIN sys_catalog.sys_description d
ON CAST(d.objoid AS varchar(64)) = CAST(c.oid AS varchar(64)) AND d.objsubid = 0
WHERE CAST(v.schemaname AS varchar(256)) = ?
UNION ALL
SELECT CAST(mv.matviewname AS varchar(256)) AS table_name,
'MATERIALIZED_VIEW' AS table_type,
CAST(d.description AS varchar(4000)) AS remarks
FROM sys_catalog.sys_matviews mv
JOIN sys_catalog.sys_namespace n
ON CAST(n.nspname AS varchar(256)) = CAST(mv.schemaname AS varchar(256))
JOIN sys_catalog.sys_class c
ON CAST(c.relnamespace AS varchar(64)) = CAST(n.oid AS varchar(64))
AND CAST(c.relname AS varchar(256)) = CAST(mv.matviewname AS varchar(256))
LEFT JOIN sys_catalog.sys_description d
ON CAST(d.objoid AS varchar(64)) = CAST(c.oid AS varchar(64)) AND d.objsubid = 0
WHERE CAST(mv.schemaname AS varchar(256)) = ?
) metadata_tables
ORDER BY table_name
""";
}
private static String kingbaseEffectiveSchema(Connection conn, String schema) {
String effectiveSchema = emptyToNull(schema);
if (effectiveSchema != null) {
return effectiveSchema;
}
try {
effectiveSchema = emptyToNull(conn.getSchema());
} catch (SQLException | AbstractMethodError | UnsupportedOperationException ignored) {
}
return effectiveSchema == null ? "PUBLIC" : effectiveSchema;
}
private static JsonNode kingbaseGetColumns(Connection conn, String schema, String table) throws SQLException {
ArrayNode result = MAPPER.createArrayNode();
String effectiveSchema = emptyToNull(schema) == null ? "PUBLIC" : schema;
String effectiveSchema = kingbaseEffectiveSchema(conn, schema);
Set<String> primaryKeys = kingbasePrimaryKeys(conn, effectiveSchema, table);
String sql = "SELECT a.attname AS column_name, " +
"format_type(a.atttypid, a.atttypmod) AS data_type, " +

View File

@ -1061,6 +1061,115 @@ final class DbxJdbcPluginTest {
assertEquals(true, sql.get(1).contains("FROM sys_catalog.sys_attribute"));
}
@Test
void kingbaseListTablesReusesCastSafeAgentDiscovery() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("kingbaseListTables", Connection.class, String.class, boolean.class);
method.setAccessible(true);
List<String> sql = new ArrayList<>();
ResultSet tables = rowsResultSet(
new String[] { "table_name", "table_type", "remarks" },
new Object[][] {
{ "orders", "TABLE", "Order records" },
{ "order_summary", "VIEW", null }
}
);
JsonNode result = (JsonNode) method.invoke(null, kingbaseTableConnection(sql, tables, false), "APP", false);
assertEquals("orders", result.path(0).path("name").asText());
assertEquals("TABLE", result.path(0).path("table_type").asText());
assertEquals("Order records", result.path(0).path("comment").asText());
assertEquals("VIEW", result.path(1).path("table_type").asText());
String discoverySql = sql.get(2);
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_class c"));
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_tables t"));
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_foreign_table ft"));
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_views"));
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_matviews"));
assertEquals(true, discoverySql.contains("CAST(c.relname AS varchar(256))"));
assertEquals(false, discoverySql.contains("relkind"));
assertEquals(false, discoverySql.contains("sys_freespace"));
assertEquals(false, discoverySql.contains("pg_relation_size_ex"));
}
@Test
void kingbaseCompatibilityListTablesAvoidsRelkind() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("kingbaseListTables", Connection.class, String.class, boolean.class);
method.setAccessible(true);
List<String> sql = new ArrayList<>();
ResultSet tables = rowsResultSet(
new String[] { "table_name", "table_type", "remarks" },
new Object[][] { { "orders", "BASE TABLE", null }, { "order_summary", "VIEW", null } }
);
JsonNode result = (JsonNode) method.invoke(null, kingbaseTableConnection(sql, tables, true), "APP", false);
assertEquals("TABLE", result.path(0).path("table_type").asText());
assertEquals("VIEW", result.path(1).path("table_type").asText());
assertEquals(true, sql.get(1).contains("LOWER(name) = 'database_mode'"));
String discoverySql = sql.get(2);
assertEquals(true, discoverySql.contains("FROM information_schema.tables"));
assertEquals(false, discoverySql.contains("relkind"));
assertEquals(false, discoverySql.contains("sys_freespace"));
}
@Test
void kingbasePostgresCatalogModePreservesMaterializedViews() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("kingbaseListTables", Connection.class, String.class, boolean.class);
method.setAccessible(true);
List<String> sql = new ArrayList<>();
ResultSet tables = rowsResultSet(
new String[] { "table_name", "table_type", "remarks" },
new Object[][] { { "orders", "TABLE", null }, { "order_summary", "MATERIALIZED_VIEW", "Cached orders" } }
);
JsonNode result = (JsonNode) method.invoke(null, kingbasePostgresTableConnection(sql, tables), "APP", false);
assertEquals("TABLE", result.path(0).path("table_type").asText());
assertEquals("MATERIALIZED_VIEW", result.path(1).path("table_type").asText());
assertEquals("SELECT 1 FROM sys_catalog.sys_namespace WHERE 1 = 0", sql.get(0));
assertEquals("SELECT 1 FROM pg_catalog.pg_namespace WHERE 1 = 0", sql.get(1));
String discoverySql = sql.get(2);
assertEquals(true, discoverySql.contains("FROM pg_catalog.pg_class c"));
assertEquals(true, discoverySql.contains("JOIN pg_catalog.pg_namespace n"));
assertEquals(true, discoverySql.contains("c.relkind IN ('r', 'p', 'v', 'm', 'f')"));
}
@Test
void kingbaseRegularTableDiscoveryExcludesCompositeTypesWithPositiveTableCatalog() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("kingbaseListTables", Connection.class, String.class, boolean.class);
method.setAccessible(true);
List<String> sql = new ArrayList<>();
JsonNode result = (JsonNode) method.invoke(null, kingbaseCompositeCatalogConnection(sql), "APP", false);
assertEquals(1, result.size());
assertEquals("orders", result.path(0).path("name").asText());
String discoverySql = sql.get(2);
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_tables t"));
assertEquals(true, discoverySql.contains("FROM sys_catalog.sys_foreign_table ft"));
assertEquals(false, discoverySql.contains("information_schema.tables"));
assertEquals(false, discoverySql.contains("sys_rewrite"));
assertEquals(false, discoverySql.contains("sys_index"));
}
@Test
void kingbaseEffectiveSchemaPreservesConnectionSchemaCase() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("kingbaseEffectiveSchema", Connection.class, String.class);
method.setAccessible(true);
Connection connection = (Connection) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Connection.class },
(proxy, invokedMethod, args) -> switch (invokedMethod.getName()) {
case "getSchema" -> "CaseSensitiveSchema";
default -> defaultValue(invokedMethod.getReturnType());
}
);
assertEquals("CaseSensitiveSchema", method.invoke(null, connection, null));
assertEquals("ExplicitSchema", method.invoke(null, connection, "ExplicitSchema"));
}
@Test
void columnIsNullablePrefersIsNullableStringWhenNullableCodeIsWrong() throws Exception {
Method method = DbxJdbcPlugin.class.getDeclaredMethod("columnIsNullable", ResultSet.class);
@ -1448,6 +1557,113 @@ final class DbxJdbcPluginTest {
);
}
private static Connection kingbaseTableConnection(List<String> sql, ResultSet rs, boolean compatibilityMode) {
return (Connection) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Connection.class },
(proxy, method, args) -> switch (method.getName()) {
case "createStatement" -> kingbaseCatalogProbeStatement(sql, compatibilityMode);
case "prepareStatement" -> {
sql.add(String.valueOf(args[0]));
yield preparedStatement(rs);
}
case "isClosed" -> false;
case "close" -> null;
default -> defaultValue(method.getReturnType());
}
);
}
private static Connection kingbaseCompositeCatalogConnection(List<String> sql) {
return (Connection) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Connection.class },
(proxy, method, args) -> switch (method.getName()) {
case "createStatement" -> kingbaseCatalogProbeStatement(sql, false);
case "prepareStatement" -> {
String preparedSql = String.valueOf(args[0]);
sql.add(preparedSql);
boolean positivelySelectsTables = preparedSql.contains("FROM sys_catalog.sys_tables t")
&& preparedSql.contains("FROM sys_catalog.sys_foreign_table ft");
Object[][] rows = positivelySelectsTables
? new Object[][] { { "orders", "TABLE", null } }
: new Object[][] { { "orders", "TABLE", null }, { "address_type", "TABLE", null } };
yield preparedStatement(rowsResultSet(new String[] { "table_name", "table_type", "remarks" }, rows));
}
case "isClosed" -> false;
case "close" -> null;
default -> defaultValue(method.getReturnType());
}
);
}
private static Connection kingbasePostgresTableConnection(List<String> sql, ResultSet rs) {
return (Connection) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Connection.class },
(proxy, method, args) -> switch (method.getName()) {
case "createStatement" -> kingbasePostgresCatalogProbeStatement(sql);
case "prepareStatement" -> {
sql.add(String.valueOf(args[0]));
yield preparedStatement(rs);
}
case "isClosed" -> false;
case "close" -> null;
default -> defaultValue(method.getReturnType());
}
);
}
private static Statement kingbaseCatalogProbeStatement(List<String> sql, boolean compatibilityMode) {
return (Statement) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Statement.class },
(proxy, method, args) -> switch (method.getName()) {
case "executeQuery" -> {
String query = String.valueOf(args[0]);
sql.add(query);
if (query.contains("sys_catalog.sys_namespace")) {
yield rowsResultSet(new String[] { "exists" }, new Object[0][]);
}
if (query.contains("LOWER(name) = 'database_mode'")) {
yield rowsResultSet(
new String[] { "setting" },
new Object[][] { { compatibilityMode ? "mysql" : "oracle" } }
);
}
if (query.contains("LOWER(name) = 'sql_mode'")) {
yield rowsResultSet(new String[] { "exists" }, new Object[0][]);
}
throw new SQLException("Unexpected Kingbase catalog probe: " + query);
}
case "close" -> null;
default -> defaultValue(method.getReturnType());
}
);
}
private static Statement kingbasePostgresCatalogProbeStatement(List<String> sql) {
return (Statement) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),
new Class<?>[] { Statement.class },
(proxy, method, args) -> switch (method.getName()) {
case "executeQuery" -> {
String query = String.valueOf(args[0]);
sql.add(query);
if (query.contains("sys_catalog.sys_namespace")) {
throw new SQLException("relation does not exist: sys_catalog.sys_namespace");
}
if (query.contains("pg_catalog.pg_namespace")) {
yield rowsResultSet(new String[] { "exists" }, new Object[0][]);
}
throw new SQLException("Unexpected Kingbase catalog probe: " + query);
}
case "close" -> null;
default -> defaultValue(method.getReturnType());
}
);
}
private static Statement statement(List<String> sql, ResultSet rs) {
return (Statement) Proxy.newProxyInstance(
DbxJdbcPluginTest.class.getClassLoader(),