fix(kingbase): support PostgreSQL catalog mode

This commit is contained in:
t8y2 2026-07-12 21:28:46 +08:00
parent 8fa97fa926
commit 6368c49c16
2 changed files with 77 additions and 0 deletions

View File

@ -44,6 +44,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
private static final String KINGBASE_VIEW_SCHEMA = "CAST(v.schemaname AS varchar(256))";
private static final String KINGBASE_MATVIEW_NAME = "CAST(mv.matviewname AS varchar(256))";
private static final String KINGBASE_MATVIEW_SCHEMA = "CAST(mv.schemaname AS varchar(256))";
private boolean postgresCatalogMode;
public static final PostgresLikeAgentProfile KINGBASE_PROFILE = new PostgresLikeAgentProfile(
"com.kingbase8.Driver",
@ -56,13 +57,28 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
protected void afterConnect(ConnectParams params, Connection connection) {
postgresCatalogMode = false;
if (params.isMysql_compat_mode()) {
setMysqlCompatMode(true);
return;
}
postgresCatalogMode = !catalogExists(connection, "sys_catalog.sys_namespace")
&& catalogExists(connection, "pg_catalog.pg_namespace");
}
private static boolean catalogExists(Connection connection, String catalog) {
try (Statement stmt = connection.createStatement();
ResultSet ignored = stmt.executeQuery("SELECT 1 FROM " + catalog + " WHERE 1 = 0")) {
return true;
} catch (Exception ignored) {
// Kingbase compatibility modes expose different catalog families.
return false;
}
}
@Override
public List<DatabaseInfo> listDatabases() {
if (postgresCatalogMode) return super.listDatabases();
return unchecked(() -> {
if (isMysqlCompatMode()) {
List<DatabaseInfo> result = queryDatabases("SELECT current_database() AS database_name");
@ -96,6 +112,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<String> listSchemas() {
if (postgresCatalogMode) return super.listSchemas();
return unchecked(() -> {
List<String> result = new ArrayList<>();
String sql = isMysqlCompatMode()
@ -122,6 +139,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<TableInfo> listTables(String schema) {
if (postgresCatalogMode) return super.listTables(schema);
if (isMysqlCompatMode()) {
return listTables(schema, "table_type IN ('BASE TABLE', 'VIEW')");
}
@ -130,6 +148,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<TableInfo> listTables(String schema, MetadataListConstraints constraints) {
if (postgresCatalogMode) return super.listTables(schema, constraints);
MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints);
if (isUnconstrained(normalized)) {
return listTables(schema);
@ -199,6 +218,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<ObjectInfo> listObjects(String schema) {
if (postgresCatalogMode) return super.listObjects(schema);
return unchecked(() -> {
String effectiveSchema = effectiveSchema(schema);
List<ObjectInfo> result = new ArrayList<>();
@ -235,6 +255,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<ObjectInfo> listObjects(String schema, MetadataListConstraints constraints) {
if (postgresCatalogMode) return super.listObjects(schema, constraints);
MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints);
if (isUnconstrained(normalized)) {
return listObjects(schema);
@ -299,6 +320,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public ObjectSource getObjectSource(String schema, String name, String objectType) {
if (postgresCatalogMode) return super.getObjectSource(schema, name, objectType);
if ("FUNCTION".equalsIgnoreCase(objectType) || "PROCEDURE".equalsIgnoreCase(objectType)) {
return routineSource(schema, name, objectType);
}
@ -354,6 +376,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<ColumnInfo> getColumns(String schema, String table) {
if (postgresCatalogMode) return super.getColumns(schema, table);
return unchecked(() -> {
Set<String> primaryKeys = primaryKeys(schema, table);
if (!isMysqlCompatMode()) {
@ -444,6 +467,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<IndexInfo> listIndexes(String schema, String table) {
if (postgresCatalogMode) return super.listIndexes(schema, table);
return unchecked(() -> {
Map<String, CatalogIndexBuilder> indexes = new LinkedHashMap<>();
String sql = "SELECT i.relname AS index_name, am.amname AS index_type, " +
@ -487,6 +511,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<ForeignKeyInfo> listForeignKeys(String schema, String table) {
if (postgresCatalogMode) return super.listForeignKeys(schema, table);
return unchecked(() -> {
List<ForeignKeyInfo> result = new ArrayList<>();
String sql = "SELECT fk.constraint_name, fk.column_name, pk.table_name AS ref_table, pk.column_name AS ref_column " +
@ -525,11 +550,13 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<TriggerInfo> listTriggers(String schema, String table) {
if (postgresCatalogMode) return super.listTriggers(schema, table);
return Collections.emptyList();
}
@Override
public String setSchemaSQL(String schema) {
if (postgresCatalogMode) return super.setSchemaSQL(schema);
// Kingbase searches sys_catalog implicitly before user schemas unless it
// is listed explicitly. Put it after the selected schema so business
// tables named like system tables (for example sys_config) win.

View File

@ -1,6 +1,7 @@
package com.dbx.agent.kingbase;
import com.dbx.agent.ColumnInfo;
import com.dbx.agent.ConnectParams;
import com.dbx.agent.DatabaseAgent;
import com.dbx.agent.DatabaseInfo;
import com.dbx.agent.IndexInfo;
@ -115,6 +116,27 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertFalse(sql.get(0).contains("SYS%"), sql.get(0));
}
@Test
void postgresCompatModeUsesPostgresCatalogForMetadata() throws Exception {
List<String> sql = new ArrayList<>();
KingbaseAgent agent = new KingbaseAgent();
Connection connection = postgresCatalogConnection(sql, resultSet(
new String[]{"schema_name"},
new Object[][]{{"public"}}
));
Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class);
afterConnect.setAccessible(true);
afterConnect.invoke(agent, new ConnectParams(), connection);
TestSupport.setPrivateConnection(agent, connection);
Assertions.assertEquals(List.of("public"), agent.listSchemas());
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_namespace"), sql.get(2));
Assertions.assertEquals("SET search_path TO \"app\"", agent.setSchemaSQL("app"));
}
@Test
void mysqlCompatListTablesUsesInformationSchema() {
List<String> sql = new ArrayList<>();
@ -462,6 +484,34 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
});
}
private static Connection postgresCatalogConnection(List<String> sql, ResultSet metadataResult) {
return proxy(Connection.class, (method, args) -> {
if ("createStatement".equals(method.getName())) {
return proxy(Statement.class, (statementMethod, statementArgs) -> {
if ("executeQuery".equals(statementMethod.getName())) {
String query = String.valueOf(statementArgs[0]);
sql.add(query);
if (query.contains("sys_catalog.sys_namespace")) {
throw new SQLException("relation does not exist: sys_catalog.sys_namespace");
}
return resultSet(new String[]{"probe"}, new Object[][]{});
}
return defaultValue(statementMethod.getReturnType());
});
}
if ("prepareStatement".equals(method.getName())) {
String query = String.valueOf(args[0]);
sql.add(query);
return proxy(PreparedStatement.class, (statementMethod, statementArgs) -> {
if ("executeQuery".equals(statementMethod.getName())) return metadataResult;
return defaultValue(statementMethod.getReturnType());
});
}
if ("isClosed".equals(method.getName())) return false;
return defaultValue(method.getReturnType());
});
}
private static ResultSet resultSet(String[] columns, Object[][] rows) {
int[] index = {-1};
return proxy(ResultSet.class, (method, args) -> {