fix(jdbc): avoid privileged Kingbase metadata query
This commit is contained in:
parent
039ec43440
commit
d69f9d3cde
|
|
@ -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')")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, " +
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue