diff --git a/.github/workflows/agents-release.yml b/.github/workflows/agents-release.yml index 28f31c0b3..0fc101c9d 100644 --- a/.github/workflows/agents-release.yml +++ b/.github/workflows/agents-release.yml @@ -89,7 +89,6 @@ jobs: name: agent-jars path: | agents/drivers/*/build/libs/dbx-agent-*.jar - !agents/drivers/kingbase/build/libs/dbx-agent-kingbase.jar build-oracle-native: needs: [bump-versions] @@ -306,7 +305,7 @@ jobs: - name: Flatten artifacts run: | mkdir -p release - find artifacts/agent-jars -name '*.jar' ! -name 'dbx-agent-kingbase.jar' -exec cp {} release/ \; + find artifacts/agent-jars -name '*.jar' -exec cp {} release/ \; find artifacts/oracle-native -type f -name 'dbx-agent-oracle-*' -exec cp {} release/ \; find artifacts/xugu-native -type f -name 'dbx-agent-xugu-*' -exec cp {} release/ \; find artifacts/kingbase-native -type f -name 'dbx-agent-kingbase-*' -exec cp {} release/ \; diff --git a/agents/drivers/kingbase-go/main_test.go b/agents/drivers/kingbase-go/main_test.go index e6f114ce1..783399810 100644 --- a/agents/drivers/kingbase-go/main_test.go +++ b/agents/drivers/kingbase-go/main_test.go @@ -22,6 +22,8 @@ var registerExpressionFallbackDriver sync.Once var expressionFallbackState atomic.Pointer[fallbackDriverState] var registerModeDetectionDriver sync.Once var modeDetectionState atomic.Pointer[modeDetectionDriverState] +var registerMetadataDriver sync.Once +var metadataState atomic.Pointer[metadataDriverState] type fakeDriverState struct { queryArgs int @@ -63,6 +65,18 @@ type modeDetectionConn struct { state *modeDetectionDriverState } +type metadataDriverState struct { + mu sync.Mutex + queries []string + query func(string) (driver.Rows, error) +} + +type metadataDriver struct{} + +type metadataConn struct { + state *metadataDriverState +} + type valueRows struct { columns []string rows [][]driver.Value @@ -189,6 +203,23 @@ func (connection *modeDetectionConn) QueryContext(_ context.Context, query strin } } +func (metadataDriver) Open(string) (driver.Conn, error) { + return &metadataConn{state: metadataState.Load()}, nil +} + +func (*metadataConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip } + +func (*metadataConn) Close() error { return nil } + +func (*metadataConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip } + +func (connection *metadataConn) QueryContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Rows, error) { + connection.state.mu.Lock() + connection.state.queries = append(connection.state.queries, query) + connection.state.mu.Unlock() + return connection.state.query(query) +} + func (rows *valueRows) Columns() []string { return rows.columns } func (*valueRows) Close() error { return nil } @@ -229,6 +260,19 @@ func openModeDetectionDB(t *testing.T, state *modeDetectionDriverState) *sql.DB return db } +func openMetadataDB(t *testing.T, state *metadataDriverState) *sql.DB { + t.Helper() + registerMetadataDriver.Do(func() { sql.Register("kingbase-metadata-test", metadataDriver{}) }) + metadataState.Store(state) + db, err := sql.Open("kingbase-metadata-test", "") + if err != nil { + t.Fatal(err) + } + db.SetMaxOpenConns(1) + t.Cleanup(func() { _ = db.Close() }) + return db +} + func TestHandshakeAdvertisesMultiSession(t *testing.T) { runtime := &runtimeServer{sessions: map[string]*agentSession{}} result, shutdown, err := runtime.dispatch("handshake", nil) @@ -320,6 +364,106 @@ func TestMetadataNormalizationHelpers(t *testing.T) { } } +func TestListDatabasesFallsBackToPostgresCatalog(t *testing.T) { + state := &metadataDriverState{query: func(query string) (driver.Rows, error) { + switch { + case strings.Contains(query, "sys_catalog.sys_database"): + return nil, errors.New("sys catalog unavailable") + case strings.Contains(query, "pg_catalog.pg_database"): + return &valueRows{columns: []string{"datname"}, rows: [][]driver.Value{{"app"}, {"test"}}}, nil + default: + return nil, errors.New("unexpected query: " + query) + } + }} + server := newServer() + server.db = openMetadataDB(t, state) + server.params.Database = "configured" + + databases, err := server.listDatabases() + if err != nil { + t.Fatal(err) + } + if len(databases) != 2 || databases[0].Name != "app" || databases[1].Name != "test" { + t.Fatalf("unexpected databases: %#v", databases) + } + state.mu.Lock() + defer state.mu.Unlock() + if len(state.queries) != 2 || !strings.Contains(state.queries[0], "sys_catalog.sys_database") || !strings.Contains(state.queries[1], "pg_catalog.pg_database") { + t.Fatalf("catalog fallback order changed: %v", state.queries) + } +} + +func TestListTablesPreservesKingbaseObjectTypesAndComments(t *testing.T) { + state := &metadataDriverState{query: func(query string) (driver.Rows, error) { + if !strings.Contains(query, "FROM sys_catalog.sys_class c") || !strings.Contains(query, "c.relkind IN ('r','p','v','m','f')") { + return nil, errors.New("unexpected query: " + query) + } + return &valueRows{ + columns: []string{"relname", "relkind", "comment"}, + rows: [][]driver.Value{ + {"orders", "TABLE", "orders table"}, + {"sales_view", "VIEW", nil}, + {"sales_cache", "MATERIALIZED_VIEW", "cached sales"}, + }, + }, nil + }} + server := newServer() + server.db = openMetadataDB(t, state) + + tables, err := server.listTables("public", metadataListConstraints{Filter: "sales", ObjectTypes: []string{"VIEW", "MATERIALIZED_VIEW"}}) + if err != nil { + t.Fatal(err) + } + if len(tables) != 2 || tables[0].TableType != "VIEW" || tables[1].TableType != "MATERIALIZED_VIEW" { + t.Fatalf("unexpected tables: %#v", tables) + } + if tables[1].Comment == nil || *tables[1].Comment != "cached sales" { + t.Fatalf("materialized view comment was lost: %#v", tables[1]) + } +} + +func TestListTriggersUsesCompatibilityCatalogAndDecodesTiming(t *testing.T) { + state := &metadataDriverState{query: func(query string) (driver.Rows, error) { + if !strings.Contains(query, "FROM pg_catalog.pg_trigger") || !strings.Contains(query, "NOT tg.tgisinternal") { + return nil, errors.New("unexpected query: " + query) + } + return &valueRows{ + columns: []string{"tgname", "event", "tgtype"}, + rows: [][]driver.Value{{"orders_before", "INSERT,UPDATE", int64(2)}, {"orders_instead", "DELETE", int64(64)}}, + }, nil + }} + server := newServer() + server.db = openMetadataDB(t, state) + server.mode.postgresCatalog = true + + triggers, err := server.listTriggers("public", "orders") + if err != nil { + t.Fatal(err) + } + if len(triggers) != 2 || triggers[0].Timing != "BEFORE" || triggers[1].Timing != "INSTEAD OF" { + t.Fatalf("unexpected triggers: %#v", triggers) + } +} + +func TestRoutineSourceUsesKingbaseCatalogFunction(t *testing.T) { + state := &metadataDriverState{query: func(query string) (driver.Rows, error) { + if !strings.Contains(query, "SELECT sys_get_functiondef(p.oid)") || !strings.Contains(query, "FROM sys_catalog.sys_proc") { + return nil, errors.New("unexpected query: " + query) + } + return &valueRows{columns: []string{"source"}, rows: [][]driver.Value{{"CREATE FUNCTION public.format_name() RETURNS text AS $$ SELECT 'x'; $$"}}}, nil + }} + server := newServer() + server.db = openMetadataDB(t, state) + + source, err := server.getObjectSource("public", "format_name", "FUNCTION") + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(source["source"].(string), "CREATE FUNCTION public.format_name()") { + t.Fatalf("unexpected routine source: %#v", source) + } +} + func TestColumnsFallbackToPgGetExprAndCacheChoice(t *testing.T) { registerExpressionFallbackDriver.Do(func() { sql.Register("kingbase-expression-fallback-test", fallbackDriver{}) }) state := &fallbackDriverState{} diff --git a/agents/drivers/kingbase/build.gradle b/agents/drivers/kingbase/build.gradle deleted file mode 100644 index 7c9eef176..000000000 --- a/agents/drivers/kingbase/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'cn.com.kingbase:kingbase8:9.0.1.jre7' -} - -tasks.named('shadowJar') { - manifest { - attributes('Agent-Label': '人大金仓 KingbaseES', 'Main-Class': 'com.dbx.agent.kingbase.KingbaseAgent') - } -} diff --git a/agents/drivers/kingbase/src/main/java/com/dbx/agent/kingbase/KingbaseAgent.java b/agents/drivers/kingbase/src/main/java/com/dbx/agent/kingbase/KingbaseAgent.java deleted file mode 100644 index cfebd0073..000000000 --- a/agents/drivers/kingbase/src/main/java/com/dbx/agent/kingbase/KingbaseAgent.java +++ /dev/null @@ -1,1098 +0,0 @@ -package com.dbx.agent.kingbase; - -import com.dbx.agent.ColumnInfo; -import com.dbx.agent.ConnectParams; -import com.dbx.agent.DatabaseInfo; -import com.dbx.agent.ForeignKeyInfo; -import com.dbx.agent.IndexInfo; -import com.dbx.agent.JdbcIdentifiers; -import com.dbx.agent.MultiSessionJsonRpcServer; -import com.dbx.agent.MetadataListConstraints; -import com.dbx.agent.MetadataSqlSupport; -import com.dbx.agent.ObjectInfo; -import com.dbx.agent.ObjectSource; -import com.dbx.agent.PostgresLikeAgent; -import com.dbx.agent.PostgresLikeAgentProfile; -import com.dbx.agent.TableInfo; -import com.dbx.agent.TriggerInfo; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class KingbaseAgent extends PostgresLikeAgent { - private static final int TRIGGER_TYPE_BEFORE = 1 << 1; - private static final int TRIGGER_TYPE_INSTEAD = 1 << 6; - private static final int KINGBASE_VOID_TYPE_OID = 2278; - private static final String KINGBASE_REL_NAME = "CAST(c.relname AS varchar(256))"; - private static final String KINGBASE_REL_OID = "CAST(c.oid AS varchar(64))"; - private static final String KINGBASE_REL_NAMESPACE = "CAST(c.relnamespace AS varchar(64))"; - private static final String KINGBASE_REL_OWNER = "c.relowner"; - private static final String KINGBASE_SCHEMA_NAME = "CAST(n.nspname AS varchar(256))"; - private static final String KINGBASE_NAMESPACE_OID = "CAST(n.oid AS varchar(64))"; - private static final String KINGBASE_DESCRIPTION = "CAST(d.description AS varchar(4000))"; - private static final String KINGBASE_ROUTINE_NAME = "CAST(p.proname AS varchar(256))"; - private static final String KINGBASE_ROUTINE_OID = "CAST(p.oid AS varchar(64))"; - private static final String KINGBASE_ROUTINE_NAMESPACE = "CAST(p.pronamespace AS varchar(64))"; - private static final String KINGBASE_VIEW_NAME = "CAST(v.viewname AS varchar(256))"; - 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 static final Pattern BOUNDED_VARCHAR_TYPE = Pattern.compile( - "^(?:varchar|character\\s+varying)\\s*\\(\\s*(\\d+)\\s*\\)$", - Pattern.CASE_INSENSITIVE - ); - private boolean postgresCatalogMode; - private boolean sqlServerIdentityCatalogMode; - private volatile boolean usePgDefaultExpressionFunction; - - public static final PostgresLikeAgentProfile KINGBASE_PROFILE = new PostgresLikeAgentProfile( - "com.kingbase8.Driver", - "jdbc:kingbase8://{host}:{port}/{database}" - ); - - public KingbaseAgent() { - super(KINGBASE_PROFILE); - } - - @Override - protected void afterConnect(ConnectParams params, Connection connection) { - postgresCatalogMode = false; - sqlServerIdentityCatalogMode = false; - usePgDefaultExpressionFunction = false; - setMysqlCompatMode(params.isMysql_compat_mode()); - if (params.isMysql_compat_mode()) { - return; - } - postgresCatalogMode = !catalogExists(connection, "sys_catalog.sys_namespace") - && catalogExists(connection, "pg_catalog.pg_namespace"); - if (!postgresCatalogMode && detectMysqlCompatMode(connection)) { - setMysqlCompatMode(true); - } - // SQLServer compatibility exposes identity metadata through this catalog only. - sqlServerIdentityCatalogMode = !postgresCatalogMode - && !isMysqlCompatMode() - && catalogExists(connection, "sys.identity_columns"); - } - - private static boolean detectMysqlCompatMode(Connection connection) { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.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. - } - return mysqlSqlModeExists(connection); - } - - private static boolean mysqlSqlModeExists(Connection connection) { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT 1 FROM sys_catalog.sys_settings WHERE LOWER(name) = 'sql_mode'")) { - return rs.next(); - } catch (Exception ignored) { - return false; - } - } - - 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 listDatabases() { - if (postgresCatalogMode) return super.listDatabases(); - return unchecked(() -> { - for (String sql : List.of( - "SELECT datname AS database_name FROM sys_catalog.sys_database WHERE datistemplate = false AND datallowconn = true ORDER BY datname", - "SELECT datname AS database_name FROM pg_catalog.pg_database WHERE datistemplate = false AND datallowconn = true ORDER BY datname" - )) { - try { - List result = queryDatabases(sql); - if (!result.isEmpty()) return result; - } catch (Exception ignored) { - // Kingbase catalog names differ across compatibility modes and versions. - } - } - if (isMysqlCompatMode()) { - try { - List result = queryDatabases("SELECT current_database() AS database_name"); - if (!result.isEmpty()) return result; - } catch (Exception ignored) { - // Keep the configured database as the final fallback if current_database() is unavailable. - } - } - return Collections.singletonList(new DatabaseInfo(getConfiguredDatabase())); - }); - } - - private List queryDatabases(String sql) throws Exception { - try (PreparedStatement stmt = requireConnected().prepareStatement(sql); - ResultSet rs = stmt.executeQuery()) { - List result = new ArrayList<>(); - while (rs.next()) { - result.add(new DatabaseInfo(rs.getString("database_name"))); - } - return result; - } - } - - @Override - public List listSchemas() { - if (postgresCatalogMode) return super.listSchemas(); - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = isMysqlCompatMode() - ? "SELECT schema_name " + - "FROM information_schema.schemata " + - "WHERE UPPER(schema_name) <> 'INFORMATION_SCHEMA' " + - "AND UPPER(schema_name) NOT LIKE 'SYS\\_%' ESCAPE '\\' " + - "AND UPPER(schema_name) NOT LIKE 'XLOG\\_%' ESCAPE '\\' " + - "ORDER BY schema_name" - : "SELECT nspname AS schema_name " + - "FROM sys_catalog.sys_namespace " + - "WHERE nspname NOT LIKE 'sys_temp_%' " + - "AND nspname NOT LIKE 'sys_toast_temp_%' " + - "ORDER BY nspname"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql); - ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(rs.getString("schema_name")); - } - } - return result; - }); - } - - @Override - public List listTables(String schema) { - if (postgresCatalogMode) return super.listTables(schema); - if (isMysqlCompatMode()) { - return queryMysqlCompatTables(schema, MetadataListConstraints.NONE); - } - return queryRegularTables(schema, MetadataListConstraints.NONE); - } - - @Override - public List listTables(String schema, MetadataListConstraints constraints) { - if (postgresCatalogMode) return super.listTables(schema, constraints); - MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints); - if (isUnconstrained(normalized)) { - return listTables(schema); - } - if (!normalized.includesTableLikeTypes()) { - return List.of(); - } - try { - return isMysqlCompatMode() - ? queryMysqlCompatTables(schema, normalized) - : queryRegularTables(schema, normalized); - } catch (RuntimeException e) { - return normalized.filterTables(listTables(schema)); - } - } - - private List queryRegularTables(String schema, MetadataListConstraints constraints) { - return unchecked(() -> { - List result = new ArrayList<>(); - List args = new ArrayList<>(); - List branches = new ArrayList<>(); - addRegularRelationBranches(branches, args, effectiveSchema(schema), constraints, "table_name", "table_type", "table_comment"); - if (branches.isEmpty()) { - return List.of(); - } - StringBuilder sql = new StringBuilder("SELECT table_name, table_type, table_comment FROM (") - .append(String.join(" UNION ALL ", branches)) - .append(") metadata_tables ORDER BY table_name"); - MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints); - try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) { - MetadataSqlSupport.bind(stmt, args); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TableInfo( - rs.getString("table_name"), - normalizeTableType(rs.getString("table_type")), - rs.getString("table_comment") - )); - } - } - } - return constraints.withoutPaging().filterTables(result); - }); - } - - private List queryMysqlCompatTables(String schema, MetadataListConstraints constraints) { - try { - return queryMysqlCompatInformationSchemaTables(schema, constraints); - } catch (RuntimeException error) { - if (!isSysFreespacePermissionError(error)) { - throw error; - } - return queryMysqlCompatCatalogTables(schema, constraints); - } - } - - private List queryMysqlCompatInformationSchemaTables( - String schema, - MetadataListConstraints constraints - ) { - return unchecked(() -> { - List result = new ArrayList<>(); - List args = new ArrayList<>(); - StringBuilder sql = new StringBuilder("SELECT t.table_name, t.table_type, ") - .append(KINGBASE_DESCRIPTION).append(" AS table_comment ") - .append("FROM information_schema.tables t ") - .append("LEFT JOIN sys_catalog.sys_namespace n ON ").append(KINGBASE_SCHEMA_NAME) - .append(" = CAST(t.table_schema AS varchar(256)) ") - .append("LEFT JOIN sys_catalog.sys_class c ON ").append(KINGBASE_REL_NAMESPACE) - .append(" = ").append(KINGBASE_NAMESPACE_OID) - .append(" AND ").append(KINGBASE_REL_NAME).append(" = CAST(t.table_name AS varchar(256)) ") - .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 t.table_schema = ").append(sqlString(effectiveSchema(schema))); - appendMysqlCompatTableTypePredicate(sql, args, constraints); - MetadataSqlSupport.appendNameFilter(sql, args, "t.table_name", constraints); - sql.append(" ORDER BY t.table_name"); - MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints); - try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) { - MetadataSqlSupport.bind(stmt, args); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TableInfo(rs.getString(1), normalizeTableType(rs.getString(2)), rs.getString(3))); - } - } - } - return constraints.withoutPaging().filterTables(result); - }); - } - - private List queryMysqlCompatCatalogTables(String schema, MetadataListConstraints constraints) { - return unchecked(() -> { - List result = new ArrayList<>(); - List args = new ArrayList<>(); - StringBuilder sql = new StringBuilder("SELECT ") - .append(KINGBASE_REL_NAME).append(" AS table_name, ") - .append("CASE WHEN CAST(c.relkind AS varchar(16)) IN ('r', 'p') THEN 'TABLE' ELSE 'VIEW' END AS table_type, ") - .append(KINGBASE_DESCRIPTION).append(" AS table_comment ") - .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(effectiveSchema(schema))); - appendMysqlCompatCatalogTypePredicate(sql, constraints); - appendRelationVisibilityPredicate(sql); - MetadataSqlSupport.appendNameFilter(sql, args, KINGBASE_REL_NAME, constraints); - sql.append(" ORDER BY ").append(KINGBASE_REL_NAME); - MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints); - try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) { - MetadataSqlSupport.bind(stmt, args); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new TableInfo( - rs.getString("table_name"), - normalizeTableType(rs.getString("table_type")), - rs.getString("table_comment") - )); - } - } - } - return constraints.withoutPaging().filterTables(result); - }); - } - - @Override - public List listObjects(String schema) { - if (postgresCatalogMode) return super.listObjects(schema); - return unchecked(() -> { - String effectiveSchema = effectiveSchema(schema); - List result = new ArrayList<>(); - for (TableInfo table : listTables(effectiveSchema)) { - result.add(new ObjectInfo(table.getName(), table.getTable_type(), effectiveSchema, table.getComment())); - } - if (isMysqlCompatMode()) { - return result; - } - - String sql = "SELECT " + KINGBASE_ROUTINE_NAME + " AS routine_name, " + - "CASE WHEN p.prorettype = " + KINGBASE_VOID_TYPE_OID + " THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type, " + - KINGBASE_DESCRIPTION + " AS routine_comment " + - "FROM sys_catalog.sys_proc p " + - "JOIN sys_catalog.sys_namespace n ON " + KINGBASE_NAMESPACE_OID + " = " + KINGBASE_ROUTINE_NAMESPACE + " " + - "LEFT JOIN sys_catalog.sys_description d ON CAST(d.objoid AS varchar(64)) = " + KINGBASE_ROUTINE_OID + " AND d.objsubid = 0 " + - "WHERE " + KINGBASE_SCHEMA_NAME + " = " + sqlString(effectiveSchema) + " " + - "ORDER BY " + KINGBASE_ROUTINE_NAME; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ObjectInfo( - rs.getString("routine_name"), - rs.getString("routine_type"), - effectiveSchema, - rs.getString("routine_comment") - )); - } - } - } - return result; - }); - } - - @Override - public List listObjects(String schema, MetadataListConstraints constraints) { - if (postgresCatalogMode) return super.listObjects(schema, constraints); - MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints); - if (isUnconstrained(normalized)) { - return listObjects(schema); - } - if (!includesSupportedObjects(normalized)) { - return List.of(); - } - try { - return isMysqlCompatMode() - ? normalized.filterObjects(toObjects(queryMysqlCompatTables(schema, normalized), effectiveSchema(schema))) - : queryRegularObjects(schema, normalized); - } catch (RuntimeException e) { - return normalized.filterObjects(listObjects(schema)); - } - } - - private List queryRegularObjects(String schema, MetadataListConstraints constraints) { - return unchecked(() -> { - String effectiveSchema = effectiveSchema(schema); - List result = new ArrayList<>(); - List branches = new ArrayList<>(); - List args = new ArrayList<>(); - if (constraints.includesTableLikeTypes()) { - addRegularRelationBranches(branches, args, effectiveSchema, constraints, "object_name", "object_type", "object_comment"); - } - if (constraints.objectTypeAllowed("PROCEDURE") || constraints.objectTypeAllowed("FUNCTION")) { - StringBuilder routineSql = new StringBuilder("SELECT ") - .append(KINGBASE_ROUTINE_NAME).append(" AS object_name, ") - .append("CASE WHEN p.prorettype = ").append(KINGBASE_VOID_TYPE_OID).append(" THEN 'PROCEDURE' ELSE 'FUNCTION' END AS object_type, ") - .append(KINGBASE_DESCRIPTION).append(" AS object_comment ") - .append("FROM sys_catalog.sys_proc p ") - .append("JOIN sys_catalog.sys_namespace n ON ").append(KINGBASE_NAMESPACE_OID).append(" = ").append(KINGBASE_ROUTINE_NAMESPACE).append(' ') - .append("LEFT JOIN sys_catalog.sys_description d ON CAST(d.objoid AS varchar(64)) = ").append(KINGBASE_ROUTINE_OID).append(" AND d.objsubid = 0 ") - .append("WHERE ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(sqlString(effectiveSchema)); - appendRoutineKindPredicate(routineSql, args, constraints); - MetadataSqlSupport.appendNameFilter(routineSql, args, KINGBASE_ROUTINE_NAME, constraints); - branches.add(routineSql.toString()); - } - if (branches.isEmpty()) { - return List.of(); - } - StringBuilder sql = new StringBuilder("SELECT object_name, object_type, object_comment FROM (") - .append(String.join(" UNION ALL ", branches)) - .append(") metadata_objects ORDER BY CASE object_type WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'MATERIALIZED_VIEW' THEN 2 WHEN 'FOREIGN_TABLE' THEN 3 WHEN 'PROCEDURE' THEN 4 WHEN 'FUNCTION' THEN 5 ELSE 9 END, object_name"); - MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints); - try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) { - MetadataSqlSupport.bind(stmt, args); - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new ObjectInfo( - rs.getString("object_name"), - rs.getString("object_type"), - effectiveSchema, - rs.getString("object_comment") - )); - } - } - } - return constraints.withoutPaging().filterObjects(result); - }); - } - - @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); - } - if (!"VIEW".equalsIgnoreCase(objectType) && !"MATERIALIZED_VIEW".equalsIgnoreCase(objectType)) { - return new ObjectSource(name, objectType, effectiveSchema(schema), ""); - } - return unchecked(() -> { - String source = ""; - String sql = "SELECT view_definition " + - "FROM information_schema.views " + - "WHERE table_schema = " + sqlString(effectiveSchema(schema)) + - " AND table_name = " + sqlString(name); - if (!isMysqlCompatMode()) { - sql = "SELECT sys_get_viewdef(c.oid) AS view_definition " + - "FROM sys_catalog.sys_class c " + - "JOIN sys_catalog.sys_namespace n ON " + KINGBASE_NAMESPACE_OID + " = " + KINGBASE_REL_NAMESPACE + " " + - "WHERE " + KINGBASE_SCHEMA_NAME + " = " + sqlString(effectiveSchema(schema)) + - " AND " + KINGBASE_REL_NAME + " = " + sqlString(name) + - " LIMIT 1"; - } - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - if (rs.next()) { - source = coalesce(rs.getString("view_definition")); - } - } - } - return new ObjectSource(name, objectType, effectiveSchema(schema), source); - }); - } - - private ObjectSource routineSource(String schema, String name, String objectType) { - return unchecked(() -> { - String source = ""; - String sql = "SELECT sys_get_functiondef(p.oid) AS source " + - "FROM sys_catalog.sys_proc p " + - "JOIN sys_catalog.sys_namespace n ON " + KINGBASE_NAMESPACE_OID + " = " + KINGBASE_ROUTINE_NAMESPACE + " " + - "WHERE " + KINGBASE_SCHEMA_NAME + " = " + sqlString(effectiveSchema(schema)) + - " AND " + KINGBASE_ROUTINE_NAME + " = " + sqlString(name) + " " + - "ORDER BY CASE WHEN p.prorettype = " + KINGBASE_VOID_TYPE_OID + " THEN " + - ("PROCEDURE".equalsIgnoreCase(objectType) ? "0 ELSE 1" : "1 ELSE 0") + - " END, p.oid LIMIT 1"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - source = coalesce(rs.getString("source")); - } - } - } - return new ObjectSource(name, objectType, effectiveSchema(schema), source); - }); - } - - @Override - public List getColumns(String schema, String table) { - if (postgresCatalogMode) return super.getColumns(schema, table); - return unchecked(() -> { - Set primaryKeys = primaryKeys(schema, table); - if (!isMysqlCompatMode()) { - return getRegularColumns(schema, table, primaryKeys); - } - return getInformationSchemaColumns(schema, table, primaryKeys); - }); - } - - private List getRegularColumns(String schema, String table, Set primaryKeys) { - boolean usePgFunction = usePgDefaultExpressionFunction; - try { - return queryRegularColumns(schema, table, primaryKeys, usePgFunction ? "pg_get_expr" : "sys_get_expr"); - } catch (RuntimeException error) { - if (usePgFunction || !isUndefinedFunction(error, "sys_get_expr")) { - throw error; - } - usePgDefaultExpressionFunction = true; - return queryRegularColumns(schema, table, primaryKeys, "pg_get_expr"); - } - } - - private List queryRegularColumns( - String schema, - String table, - Set primaryKeys, - String defaultExpressionFunction - ) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT a.attname AS column_name, " + - "format_type(a.atttypid, a.atttypmod) AS data_type, " + - "NOT a.attnotnull AS is_nullable, " + - defaultExpressionFunction + "(ad.adbin, ad.adrelid) AS column_default, " + - "d.description AS column_comment, " + - "CASE WHEN t.typname = 'numeric' AND a.atttypmod > 0 " + - "THEN ((a.atttypmod - 4) >> 16) & 65535 ELSE NULL END AS numeric_precision, " + - "CASE WHEN t.typname = 'numeric' AND a.atttypmod > 0 " + - "THEN (a.atttypmod - 4) & 65535 ELSE NULL END AS numeric_scale, " + - "CASE WHEN t.typname IN ('varchar', 'bpchar') AND a.atttypmod > 0 " + - "THEN a.atttypmod - 4 ELSE NULL END AS character_maximum_length " + - "FROM sys_catalog.sys_attribute a " + - "JOIN sys_catalog.sys_type t ON t.oid = a.atttypid " + - "JOIN sys_catalog.sys_class c ON c.oid = a.attrelid " + - "JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace " + - "LEFT JOIN sys_catalog.sys_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum " + - "LEFT JOIN sys_catalog.sys_description d ON d.objoid = a.attrelid AND d.objsubid = a.attnum " + - "WHERE n.nspname = " + sqlString(effectiveSchema(schema)) + - " AND c.relname = " + sqlString(table) + " " + - "AND a.attnum > 0 AND NOT a.attisdropped " + - "ORDER BY a.attnum"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - String columnName = rs.getString("column_name"); - result.add(new ColumnInfo( - columnName, - rs.getString("data_type"), - rs.getBoolean("is_nullable"), - rs.getString("column_default"), - primaryKeys.contains(columnName), - null, - rs.getString("column_comment"), - intObject(rs, "numeric_precision"), - intObject(rs, "numeric_scale"), - intObject(rs, "character_maximum_length") - )); - } - } - } - applySqlServerIdentityMetadata(schema, table, result); - return result; - }); - } - - private void applySqlServerIdentityMetadata(String schema, String table, List columns) { - if (!sqlServerIdentityCatalogMode || columns.isEmpty()) return; - String sql = "SELECT a.attname AS column_name, ic.seed_value AS identity_seed, " + - "ic.increment_value AS identity_increment " + - "FROM sys.identity_columns ic " + - "JOIN sys_catalog.sys_class c ON c.oid = ic.object_id " + - "JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace " + - "JOIN sys_catalog.sys_attribute a ON a.attrelid = c.oid AND a.attnum = ic.column_id " + - "WHERE n.nspname = " + sqlString(effectiveSchema(schema)) + - " AND c.relname = " + sqlString(table); - try (Statement stmt = requireConnected().createStatement(); - ResultSet rs = stmt.executeQuery(sql)) { - Map columnsByName = new LinkedHashMap<>(); - for (ColumnInfo column : columns) { - columnsByName.put(column.getName(), column); - } - while (rs.next()) { - ColumnInfo column = columnsByName.get(rs.getString("column_name")); - if (column != null) { - column.setExtra(identityExtra(rs)); - } - } - } catch (SQLException ignored) { - // Identity metadata is optional and some Kingbase versions expose a broken compatibility view. - sqlServerIdentityCatalogMode = false; - } - } - - private static String identityExtra(ResultSet rs) throws SQLException { - String seed = rs.getString("identity_seed"); - String increment = rs.getString("identity_increment"); - if (seed == null || increment == null) { - return null; - } - return "identity(" + seed + "," + increment + ")"; - } - - private List getInformationSchemaColumns(String schema, String table, Set primaryKeys) { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT ic.column_name, ic.data_type, ic.is_nullable, ic.column_default, " + - "ic.numeric_precision, ic.numeric_scale, ic.character_maximum_length, " + - "format_type(a.atttypid, a.atttypmod) AS catalog_data_type, " + - "d.description AS column_comment " + - "FROM information_schema.columns ic " + - // information_schema preserves MySQL-compatible type metadata but does not expose comments. - "LEFT JOIN sys_catalog.sys_namespace n ON n.nspname = ic.table_schema " + - "LEFT JOIN sys_catalog.sys_class c ON c.relnamespace = n.oid AND c.relname = ic.table_name " + - "LEFT JOIN sys_catalog.sys_attribute a ON a.attrelid = c.oid AND a.attname = ic.column_name " + - "AND a.attnum > 0 AND NOT a.attisdropped " + - "LEFT JOIN sys_catalog.sys_description d ON d.objoid = a.attrelid AND d.objsubid = a.attnum " + - "WHERE ic.table_schema = " + sqlString(effectiveSchema(schema)) + - " AND ic.table_name = " + sqlString(table) + " " + - "ORDER BY ic.ordinal_position"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - String columnName = rs.getString("column_name"); - String dataType = rs.getString("data_type"); - Integer characterLength = intObject(rs, "character_maximum_length"); - String catalogDataType = rs.getString("catalog_data_type"); - Integer catalogCharacterLength = boundedCharacterLength(catalogDataType); - if ("varchar".equalsIgnoreCase(dataType) - && (characterLength == null || characterLength <= 0) - && catalogCharacterLength != null) { - dataType = catalogDataType; - characterLength = catalogCharacterLength; - } - result.add(new ColumnInfo( - columnName, - dataType, - "YES".equalsIgnoreCase(coalesce(rs.getString("is_nullable"))), - rs.getString("column_default"), - primaryKeys.contains(columnName), - null, - rs.getString("column_comment"), - intObject(rs, "numeric_precision"), - intObject(rs, "numeric_scale"), - characterLength - )); - } - } - } - return result; - }); - } - - private static Integer boundedCharacterLength(String dataType) { - if (dataType == null) return null; - Matcher match = BOUNDED_VARCHAR_TYPE.matcher(dataType.trim()); - if (!match.matches()) return null; - try { - int length = Integer.parseInt(match.group(1)); - return length > 0 ? length : null; - } catch (NumberFormatException ignored) { - return null; - } - } - - @Override - public List listIndexes(String schema, String table) { - if (postgresCatalogMode) return super.listIndexes(schema, table); - return unchecked(() -> { - Map indexes = new LinkedHashMap<>(); - String sql = "SELECT i.relname AS index_name, am.amname AS index_type, " + - "ix.indisunique AS is_unique, ix.indisprimary AS is_primary, " + - "a.attname AS column_name, pos.n AS ordinal_position " + - "FROM SYS_CATALOG.SYS_INDEX ix " + - "JOIN SYS_CATALOG.SYS_CLASS t ON t.oid = ix.indrelid " + - "JOIN SYS_CATALOG.SYS_CLASS i ON i.oid = ix.indexrelid " + - "JOIN SYS_CATALOG.SYS_NAMESPACE n ON n.oid = t.relnamespace " + - "JOIN SYS_CATALOG.SYS_AM am ON am.oid = i.relam " + - "JOIN unnest(ix.indkey) WITH ORDINALITY AS pos(attnum, n) ON true " + - "JOIN SYS_CATALOG.SYS_ATTRIBUTE a ON a.attrelid = t.oid AND a.attnum = pos.attnum " + - "WHERE n.nspname = " + sqlString(effectiveSchema(schema)) + - " AND t.relname = " + sqlString(table) + " " + - "ORDER BY i.relname, pos.n"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - String name = rs.getString("index_name"); - CatalogIndexBuilder builder = indexes.get(name); - if (builder == null) { - builder = new CatalogIndexBuilder( - name, - rs.getBoolean("is_unique"), - rs.getBoolean("is_primary"), - rs.getString("index_type") - ); - indexes.put(name, builder); - } - builder.columns.add(rs.getString("column_name")); - } - } - } - List result = new ArrayList<>(); - for (CatalogIndexBuilder index : indexes.values()) { - result.add(new IndexInfo(index.name, index.columns, index.unique, index.primary, null, index.indexType, null, null)); - } - return result; - }); - } - - @Override - public List listForeignKeys(String schema, String table) { - if (postgresCatalogMode) return super.listForeignKeys(schema, table); - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT fk.constraint_name, fk.column_name, pk.table_name AS ref_table, pk.column_name AS ref_column " + - "FROM information_schema.table_constraints tc " + - "JOIN information_schema.key_column_usage fk " + - "ON fk.constraint_schema = tc.constraint_schema " + - "AND fk.constraint_name = tc.constraint_name " + - "AND fk.table_schema = tc.table_schema " + - "AND fk.table_name = tc.table_name " + - "JOIN information_schema.referential_constraints rc " + - "ON rc.constraint_schema = tc.constraint_schema " + - "AND rc.constraint_name = tc.constraint_name " + - "JOIN information_schema.key_column_usage pk " + - "ON pk.constraint_schema = rc.unique_constraint_schema " + - "AND pk.constraint_name = rc.unique_constraint_name " + - "AND pk.ordinal_position = fk.position_in_unique_constraint " + - "WHERE tc.table_schema = " + sqlString(effectiveSchema(schema)) + - " AND tc.table_name = " + sqlString(table) + " " + - "AND tc.constraint_type = 'FOREIGN KEY' " + - "ORDER BY fk.constraint_name, fk.ordinal_position"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - result.add(new ForeignKeyInfo( - rs.getString("constraint_name"), - rs.getString("column_name"), - rs.getString("ref_table"), - rs.getString("ref_column") - )); - } - } - } - return result; - }); - } - - @Override - public List listTriggers(String schema, String table) { - if (postgresCatalogMode) return super.listTriggers(schema, table); - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT tg.tgname AS trigger_name, " + - "trim(trailing ',' FROM (" + - "CASE WHEN (tg.tgtype & 4) <> 0 THEN 'INSERT,' ELSE '' END || " + - "CASE WHEN (tg.tgtype & 8) <> 0 THEN 'DELETE,' ELSE '' END || " + - "CASE WHEN (tg.tgtype & 16) <> 0 THEN 'UPDATE,' ELSE '' END || " + - "CASE WHEN (tg.tgtype & 32) <> 0 THEN 'TRUNCATE,' ELSE '' END" + - ")) AS event_manipulation, tg.tgtype AS trigger_type " + - "FROM sys_catalog.sys_trigger tg " + - "JOIN sys_catalog.sys_class c ON c.oid = tg.tgrelid " + - "JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace " + - "WHERE n.nspname = " + sqlString(effectiveSchema(schema)) + - " AND c.relname = " + sqlString(table) + " AND NOT tg.tgisinternal " + - "ORDER BY tg.tgname"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - result.add(new TriggerInfo( - rs.getString("trigger_name"), - rs.getString("event_manipulation"), - decodeTriggerTiming(rs.getInt("trigger_type")) - )); - } - } - } - return result; - }); - } - - private static String decodeTriggerTiming(int triggerType) { - // INSTEAD OF has its own catalog bit and must not fall through to AFTER. - if ((triggerType & TRIGGER_TYPE_INSTEAD) != 0) return "INSTEAD OF"; - if ((triggerType & TRIGGER_TYPE_BEFORE) != 0) return "BEFORE"; - return "AFTER"; - } - - @Override - public String setSchemaSQL(String schema) { - if (postgresCatalogMode) return super.setSchemaSQL(schema); - // Keep sys_catalog's implicit priority for functions, types, and - // operators. User table references are schema-qualified before execution. - return "SET search_path TO " + JdbcIdentifiers.INSTANCE.doubleQuote(effectiveSchema(schema)); - } - - @Override - protected Object resultValue(ResultSet rs, int index, int sqlType, String columnTypeName) { - if (isTemporalType(sqlType, columnTypeName)) { - return unchecked(() -> { - Object value = rs.getTimestamp(index); - return rs.wasNull() ? null : value.toString(); - }); - } - return super.resultValue(rs, index, sqlType, columnTypeName); - } - - private static boolean isTemporalType(int sqlType, String columnTypeName) { - switch (sqlType) { - case Types.DATE: - case Types.TIME: - case Types.TIME_WITH_TIMEZONE: - case Types.TIMESTAMP: - case Types.TIMESTAMP_WITH_TIMEZONE: - return true; - default: - break; - } - if (columnTypeName == null) { - return false; - } - String normalized = columnTypeName.trim().toLowerCase(Locale.ROOT); - return normalized.equals("date") - || normalized.equals("time") - || normalized.equals("datetime") - || normalized.startsWith("timestamp"); - } - - private Set primaryKeys(String schema, String table) { - return unchecked(() -> { - Set primaryKeys = new LinkedHashSet<>(); - String sql = "SELECT kcu.column_name " + - "FROM information_schema.table_constraints tc " + - "JOIN information_schema.key_column_usage kcu " + - "ON kcu.constraint_schema = tc.constraint_schema " + - "AND kcu.constraint_name = tc.constraint_name " + - "AND kcu.table_schema = tc.table_schema " + - "AND kcu.table_name = tc.table_name " + - "WHERE tc.table_schema = " + sqlString(effectiveSchema(schema)) + - " AND tc.table_name = " + sqlString(table) + " " + - "AND tc.constraint_type = 'PRIMARY KEY' " + - "ORDER BY kcu.ordinal_position"; - try (Statement stmt = requireConnected().createStatement()) { - try (ResultSet rs = stmt.executeQuery(sql)) { - while (rs.next()) { - primaryKeys.add(rs.getString("column_name")); - } - } - } - return primaryKeys; - }); - } - - private static boolean isUnconstrained(MetadataListConstraints constraints) { - return !constraints.hasFilter() && !constraints.hasLimit() && !constraints.hasOffset() && !constraints.hasObjectTypes(); - } - - private static boolean includesSupportedObjects(MetadataListConstraints constraints) { - return constraints.includesTableLikeTypes() - || constraints.objectTypeAllowed("PROCEDURE") - || constraints.objectTypeAllowed("FUNCTION"); - } - - private static void addRegularRelationBranches( - List branches, - List args, - String schema, - MetadataListConstraints constraints, - String nameAlias, - String typeAlias, - String commentAlias - ) { - if (constraints.tableTypeAllowed("TABLE")) { - branches.add(regularRelationBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias, "TABLE")); - } - if (constraints.tableTypeAllowed("VIEW")) { - branches.add(regularViewBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias)); - } - if (constraints.tableTypeAllowed("MATERIALIZED_VIEW")) { - branches.add(regularMaterializedViewBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias)); - } - } - - private static String regularRelationBranch( - String schema, - List args, - MetadataListConstraints constraints, - String nameAlias, - String typeAlias, - String commentAlias, - String objectType - ) { - StringBuilder sql = new StringBuilder("SELECT ") - .append(KINGBASE_REL_NAME).append(" AS ").append(nameAlias).append(", '") - .append(objectType).append("' AS ").append(typeAlias).append(", ") - .append(KINGBASE_DESCRIPTION).append(" AS ").append(commentAlias).append(' ') - .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)) - .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(); - } - - private static String regularViewBranch( - String schema, - List args, - MetadataListConstraints constraints, - String nameAlias, - String typeAlias, - String commentAlias - ) { - // sys_views/sys_matviews avoid relkind while preserving the sidebar's - // separate VIEW and MATERIALIZED_VIEW groups. - StringBuilder sql = new StringBuilder("SELECT ") - .append(KINGBASE_VIEW_NAME).append(" AS ").append(nameAlias).append(", 'VIEW' AS ").append(typeAlias).append(", ") - .append(KINGBASE_DESCRIPTION).append(" AS ").append(commentAlias).append(' ') - .append("FROM sys_catalog.sys_views v ") - .append("JOIN sys_catalog.sys_namespace n ON ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(KINGBASE_VIEW_SCHEMA).append(' ') - .append("JOIN sys_catalog.sys_class c ON ").append(KINGBASE_REL_NAMESPACE).append(" = ").append(KINGBASE_NAMESPACE_OID) - .append(" AND ").append(KINGBASE_REL_NAME).append(" = ").append(KINGBASE_VIEW_NAME).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_VIEW_SCHEMA).append(" = ").append(sqlString(schema)); - MetadataSqlSupport.appendNameFilter(sql, args, KINGBASE_VIEW_NAME, constraints); - return sql.toString(); - } - - private static String regularMaterializedViewBranch( - String schema, - List args, - MetadataListConstraints constraints, - String nameAlias, - String typeAlias, - String commentAlias - ) { - StringBuilder sql = new StringBuilder("SELECT ") - .append(KINGBASE_MATVIEW_NAME).append(" AS ").append(nameAlias).append(", 'MATERIALIZED_VIEW' AS ").append(typeAlias).append(", ") - .append(KINGBASE_DESCRIPTION).append(" AS ").append(commentAlias).append(' ') - .append("FROM sys_catalog.sys_matviews mv ") - .append("JOIN sys_catalog.sys_namespace n ON ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(KINGBASE_MATVIEW_SCHEMA).append(' ') - .append("JOIN sys_catalog.sys_class c ON ").append(KINGBASE_REL_NAMESPACE).append(" = ").append(KINGBASE_NAMESPACE_OID) - .append(" AND ").append(KINGBASE_REL_NAME).append(" = ").append(KINGBASE_MATVIEW_NAME).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_MATVIEW_SCHEMA).append(" = ").append(sqlString(schema)); - MetadataSqlSupport.appendNameFilter(sql, args, KINGBASE_MATVIEW_NAME, constraints); - return sql.toString(); - } - - 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')") - .append(" OR HAS_ANY_COLUMN_PRIVILEGE(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'))"); - } - - private static void appendMysqlCompatCatalogTypePredicate( - StringBuilder sql, - MetadataListConstraints constraints - ) { - boolean includeTables = constraints.tableTypeAllowed("TABLE"); - boolean includeViews = constraints.tableTypeAllowed("VIEW"); - if (includeTables && includeViews) { - sql.append(" AND (CAST(c.relkind AS varchar(16)) IN ('r', 'p')") - .append(" OR (CAST(c.relkind AS varchar(16)) = 'v' AND c.oid >= 16384))"); - } else if (includeTables) { - sql.append(" AND CAST(c.relkind AS varchar(16)) IN ('r', 'p')"); - } else if (includeViews) { - sql.append(" AND CAST(c.relkind AS varchar(16)) = 'v' AND c.oid >= 16384"); - } else { - sql.append(" AND 1 = 0"); - } - } - - private static void appendMysqlCompatTableTypePredicate( - StringBuilder sql, - List args, - MetadataListConstraints constraints - ) { - if (!constraints.hasObjectTypes()) { - sql.append(" AND table_type IN ('BASE TABLE', 'VIEW')"); - return; - } - List types = new ArrayList<>(); - if (constraints.tableTypeAllowed("TABLE")) { - types.add("BASE TABLE"); - } - if (constraints.tableTypeAllowed("VIEW")) { - types.add("VIEW"); - } - if (types.isEmpty()) { - sql.append(" AND 1 = 0"); - return; - } - sql.append(" AND table_type IN (").append(MetadataSqlSupport.placeholders(types.size())).append(")"); - args.addAll(types); - } - - private static boolean isSysFreespacePermissionError(Throwable error) { - boolean insufficientPrivilege = false; - boolean mentionsSysFreespace = false; - for (Throwable current = error; current != null; current = current.getCause()) { - if (current instanceof SQLException && "42501".equals(((SQLException) current).getSQLState())) { - insufficientPrivilege = true; - } - String message = current.getMessage(); - if (message != null) { - String normalized = message.toLowerCase(Locale.ROOT); - mentionsSysFreespace |= normalized.contains("sys_freespace") - || normalized.contains("pg_relation_size_ex"); - } - } - return insufficientPrivilege && mentionsSysFreespace; - } - - private static boolean isUndefinedFunction(Throwable error, String functionName) { - boolean undefinedFunction = false; - boolean mentionsFunction = false; - for (Throwable current = error; current != null; current = current.getCause()) { - if (current instanceof SQLException && "42883".equals(((SQLException) current).getSQLState())) { - undefinedFunction = true; - } - String message = current.getMessage(); - if (message != null) { - String normalized = message.toLowerCase(Locale.ROOT); - mentionsFunction |= normalized.contains(functionName.toLowerCase(Locale.ROOT)); - undefinedFunction |= normalized.contains("does not exist") || normalized.contains("不存在"); - } - } - return undefinedFunction && mentionsFunction; - } - - private static void appendRoutineKindPredicate(StringBuilder sql, List args, MetadataListConstraints constraints) { - if (!constraints.hasObjectTypes()) { - return; - } - boolean includeProcedures = constraints.objectTypeAllowed("PROCEDURE"); - boolean includeFunctions = constraints.objectTypeAllowed("FUNCTION"); - if (includeProcedures && includeFunctions) { - return; - } - if (!includeProcedures && !includeFunctions) { - sql.append(" AND 1 = 0"); - return; - } - sql.append(includeProcedures - ? " AND p.prorettype = " + KINGBASE_VOID_TYPE_OID - : " AND p.prorettype <> " + KINGBASE_VOID_TYPE_OID); - } - - private static List toObjects(List tables, String schema) { - List result = new ArrayList<>(); - for (TableInfo table : tables) { - result.add(new ObjectInfo(table.getName(), table.getTable_type(), schema, table.getComment())); - } - return result; - } - - private String effectiveSchema(String schema) { - if (schema != null && !schema.trim().isEmpty()) { - return schema; - } - return "PUBLIC"; - } - - private static Integer intObject(ResultSet rs, String column) throws Exception { - Object value = rs.getObject(column); - return value instanceof Number ? ((Number) value).intValue() : null; - } - - private static String normalizeTableType(String type) { - if (type == null || type.trim().isEmpty()) return "TABLE"; - if ("BASE TABLE".equalsIgnoreCase(type)) return "TABLE"; - return type; - } - - private static String coalesce(String value) { - return value == null ? "" : value; - } - - private static String sqlString(String value) { - return "'" + coalesce(value).replace("'", "''") + "'"; - } - - private static final class CatalogIndexBuilder { - final String name; - final boolean unique; - final boolean primary; - final String indexType; - final List columns = new ArrayList<>(); - - CatalogIndexBuilder(String name, boolean unique, boolean primary, String indexType) { - this.name = name; - this.unique = unique; - this.primary = primary; - this.indexType = indexType; - } - } - - public static void main(String[] args) { - new MultiSessionJsonRpcServer(KingbaseAgent::new).run(); - } -} diff --git a/agents/drivers/kingbase/src/test/java/com/dbx/agent/kingbase/KingbaseAgentTest.java b/agents/drivers/kingbase/src/test/java/com/dbx/agent/kingbase/KingbaseAgentTest.java deleted file mode 100644 index 51076323a..000000000 --- a/agents/drivers/kingbase/src/test/java/com/dbx/agent/kingbase/KingbaseAgentTest.java +++ /dev/null @@ -1,1205 +0,0 @@ -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.DdlBuilder; -import com.dbx.agent.IndexInfo; -import com.dbx.agent.MetadataListConstraints; -import com.dbx.agent.ObjectInfo; -import com.dbx.agent.ObjectSource; -import com.dbx.agent.TableInfo; -import com.dbx.agent.TriggerInfo; -import com.dbx.agent.test.JdbcFakeExecutionBehaviorTest; -import com.dbx.agent.test.TestSupport; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest { - @Override - protected DatabaseAgent createAgent() { - return new KingbaseAgent(); - } - - @Override - protected String resultSetSql() { - return "CALL sample_proc()"; - } - - @Test - void declaresKingbasePostgresLikeProfile() { - KingbaseAgent agent = new KingbaseAgent(); - - Assertions.assertEquals("com.kingbase8.Driver", agent.getProfile().getDriverClass()); - Assertions.assertEquals("jdbc:kingbase8://{host}:{port}/{database}", agent.getProfile().getUrlTemplate()); - } - - @Test - void schemaSwitchKeepsSystemCatalogImplicitlyFirst() { - KingbaseAgent agent = new KingbaseAgent(); - - Assertions.assertEquals("SET search_path TO \"app\"", agent.setSchemaSQL("app")); - Assertions.assertEquals("SET search_path TO \"app\"\"prod\"", agent.setSchemaSQL("app\"prod")); - } - - @Test - void mysqlCompatListDatabasesUsesKingbaseCatalog() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"database_name"}, - new Object[][]{{"app"}, {"analytics"}} - ))); - - List databases = agent.listDatabases(); - - Assertions.assertEquals(2, databases.size()); - Assertions.assertEquals("app", databases.get(0).getName()); - Assertions.assertEquals("analytics", databases.get(1).getName()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_database"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("datallowconn = true"), sql.get(0)); - } - - @Test - void mysqlCompatListDatabasesFallsBackToCurrentDatabase() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnectionWithFailures( - sql, - List.of("sys_catalog.sys_database", "FROM pg_catalog.pg_database"), - resultSet(new String[]{"database_name"}, new Object[][]{{"TEST"}}) - )); - - Assertions.assertEquals("TEST", agent.listDatabases().get(0).getName()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_database"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("datallowconn = true"), sql.get(0)); - Assertions.assertTrue(sql.get(1).contains("FROM pg_catalog.pg_database"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("datallowconn = true"), sql.get(1)); - Assertions.assertEquals("SELECT current_database() AS database_name", sql.get(2)); - } - - @Test - void regularListDatabasesUsesKingbaseCatalog() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"database_name"}, - new Object[][]{{"app"}, {"analytics"}} - ))); - - List databases = agent.listDatabases(); - Assertions.assertEquals(2, databases.size()); - Assertions.assertEquals("app", databases.get(0).getName()); - Assertions.assertEquals("analytics", databases.get(1).getName()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_database"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("datallowconn = true"), sql.get(0)); - } - - @Test - void regularListDatabasesFallsBackToPostgresCatalog() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnectionWithFailure(sql, "sys_catalog.sys_database", resultSet( - new String[]{"database_name"}, - new Object[][]{{"test"}} - ))); - - List databases = agent.listDatabases(); - - Assertions.assertEquals(1, databases.size()); - Assertions.assertEquals("test", databases.get(0).getName()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_database"), sql.get(0)); - Assertions.assertTrue(sql.get(1).contains("FROM pg_catalog.pg_database"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("datallowconn = true"), sql.get(1)); - } - - @Test - void regularListSchemasKeepsKingbaseSystemSchemas() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"schema_name"}, - new Object[][]{{"public"}, {"sys_catalog"}} - ))); - - Assertions.assertEquals(Arrays.asList("public", "sys_catalog"), agent.listSchemas()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_namespace"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("SYS%"), sql.get(0)); - } - - @Test - void mysqlCompatListSchemasFiltersSystemPrefixButKeepsUserSchemas() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"schema_name"}, - new Object[][]{{"app"}, {"systems"}} - ))); - - Assertions.assertEquals(Arrays.asList("app", "systems"), agent.listSchemas()); - Assertions.assertTrue(sql.get(0).contains("FROM information_schema.schemata"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("NOT LIKE 'SYS\\_%' ESCAPE '\\'"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("NOT LIKE 'SYS%'"), sql.get(0)); - } - - @Test - void postgresCompatModeUsesPostgresCatalogForMetadata() throws Exception { - List 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")); - Assertions.assertEquals("RESET search_path", agent.resetSchemaSQL()); - } - - @Test - void postgresCompatModePreservesMaterializedViews() throws Exception { - List 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 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 sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - Connection connection = compatibilityModeConnection(sql, "mysql", true); - - Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class); - afterConnect.setAccessible(true); - afterConnect.invoke(agent, new ConnectParams(), connection); - - Assertions.assertTrue(agent.isMysqlCompatMode()); - Assertions.assertEquals("`", agent.getIdentifierQuote()); - Assertions.assertEquals("SELECT 1 FROM sys_catalog.sys_namespace WHERE 1 = 0", sql.get(0)); - Assertions.assertTrue(sql.get(1).contains("LOWER(name) = 'database_mode'"), sql.get(1)); - Assertions.assertEquals(2, sql.size()); - } - - @Test - void legacyKingbaseFallsBackToSqlModeDetection() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - Connection connection = compatibilityModeConnection(sql, null, true); - - Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class); - afterConnect.setAccessible(true); - afterConnect.invoke(agent, new ConnectParams(), connection); - - Assertions.assertTrue(agent.isMysqlCompatMode()); - Assertions.assertTrue(sql.get(1).contains("LOWER(name) = 'database_mode'"), sql.get(1)); - Assertions.assertTrue(sql.get(2).contains("LOWER(name) = 'sql_mode'"), sql.get(2)); - } - - @Test - void oracleDatabaseModeIsNotMisdetectedFromSqlMode() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - Connection connection = compatibilityModeConnection(sql, "oracle", true); - - Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class); - afterConnect.setAccessible(true); - afterConnect.invoke(agent, new ConnectParams(), connection); - - Assertions.assertFalse(agent.isMysqlCompatMode()); - Assertions.assertTrue(sql.get(1).contains("LOWER(name) = 'database_mode'"), sql.get(1)); - Assertions.assertFalse(sql.stream().anyMatch(query -> query.contains("LOWER(name) = 'sql_mode'")), sql.toString()); - } - - @Test - void detectsSqlServerIdentityCatalogMode() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - Connection connection = compatibilityModeConnection(sql, "sqlserver", true); - - Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class); - afterConnect.setAccessible(true); - afterConnect.invoke(agent, new ConnectParams(), connection); - - Assertions.assertTrue(isSqlServerIdentityCatalogMode(agent)); - Assertions.assertEquals("SELECT 1 FROM sys_catalog.sys_namespace WHERE 1 = 0", sql.get(0)); - Assertions.assertTrue(sql.get(1).contains("LOWER(name) = 'database_mode'"), sql.get(1)); - Assertions.assertEquals("SELECT 1 FROM sys.identity_columns WHERE 1 = 0", sql.get(2)); - } - - @Test - void mysqlCompatListTablesUsesInformationSchemaAndPreservesComments() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"test_timestamps", "BASE TABLE", "timestamp samples"}} - ))); - - TableInfo table = agent.listTables("PUBLIC").get(0); - - Assertions.assertEquals("test_timestamps", table.getName()); - Assertions.assertEquals("timestamp samples", table.getComment()); - Assertions.assertTrue(sql.get(0).contains("FROM information_schema.tables"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("LEFT JOIN sys_catalog.sys_description"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("SHOW")); - } - - @Test - void mysqlCompatListTablesFallsBackFromSysFreespacePermissionError() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnectionWithMetadataFailure( - sql, - "permission denied for function sys_freespace", - "42501", - resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"orders", "TABLE", "customer orders"}} - ) - )); - - TableInfo table = agent.listTables("sales").get(0); - - Assertions.assertEquals("orders", table.getName()); - Assertions.assertEquals("customer orders", table.getComment()); - Assertions.assertEquals(2, sql.size()); - Assertions.assertTrue(sql.get(0).contains("FROM information_schema.tables"), sql.get(0)); - Assertions.assertTrue(sql.get(1).contains("FROM sys_catalog.sys_class"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("CAST(c.relkind AS varchar(16)) IN ('r', 'p')"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("c.oid >= 16384"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("HAS_TABLE_PRIVILEGE"), sql.get(1)); - Assertions.assertFalse(sql.get(1).contains("information_schema.tables"), sql.get(1)); - } - - @Test - void mysqlCompatListTablesDoesNotHideUnrelatedPermissionErrors() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnectionWithMetadataFailure( - sql, - "permission denied for relation tables", - "42501", - resultSet(new String[]{"table_name", "table_type", "table_comment"}, new Object[][]{}) - )); - - Assertions.assertThrows(RuntimeException.class, () -> agent.listTables("sales")); - Assertions.assertEquals(1, sql.size()); - } - - @Test - void constrainedMysqlCompatTableMetadataPreservesCommentsAndPaging() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"orders", "BASE TABLE", "customer orders"}} - ))); - - List tables = agent.listTables( - "sales", - new MetadataListConstraints("ord", 20, 40, List.of("TABLE")) - ); - - Assertions.assertEquals("customer orders", tables.get(0).getComment()); - Assertions.assertTrue(sql.get(0).contains("UPPER(t.table_name) LIKE ? ESCAPE '\\\\'"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("LEFT JOIN sys_catalog.sys_description"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("information_schema.tables"), sql.get(0)); - Assertions.assertTrue(sql.get(0).endsWith("LIMIT 20 OFFSET 40"), sql.get(0)); - } - - @Test - void regularListTablesUsesKingbaseCatalogAndIncludesViews() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"app_table", "TABLE", "table comment"}, {"app_view", "VIEW", "view comment"}} - ))); - - List tables = agent.listTables("public"); - - Assertions.assertEquals(2, tables.size()); - Assertions.assertEquals("app_table", tables.get(0).getName()); - 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 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 sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, compositeAwareTableConnection(sql)); - - List 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 sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, - resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"app_table", "TABLE", null}, {"app_view", "VIEW", "view comment"}} - ), - resultSet( - new String[]{"routine_name", "routine_type", "routine_comment"}, - new Object[][]{{"refresh_stats", "PROCEDURE", "proc comment"}, {"format_name", "FUNCTION", "fn comment"}} - ) - )); - - List objects = agent.listObjects("public"); - - Assertions.assertEquals(4, objects.size()); - Assertions.assertEquals("app_table", objects.get(0).getName()); - Assertions.assertEquals("TABLE", objects.get(0).getObject_type()); - Assertions.assertEquals("app_view", objects.get(1).getName()); - Assertions.assertEquals("VIEW", objects.get(1).getObject_type()); - Assertions.assertEquals("refresh_stats", objects.get(2).getName()); - Assertions.assertEquals("PROCEDURE", objects.get(2).getObject_type()); - Assertions.assertEquals("format_name", objects.get(3).getName()); - Assertions.assertEquals("FUNCTION", objects.get(3).getObject_type()); - Assertions.assertTrue(sql.get(1).contains("FROM sys_catalog.sys_proc"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("p.prorettype = 2278"), sql.get(1)); - Assertions.assertFalse(sql.get(1).contains("prokind"), sql.get(1)); - } - - @Test - void regularListTriggersDecodesTimingFromTgtype() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"trigger_name", "event_manipulation", "trigger_type"}, - new Object[][]{ - {"trg_instead_update", "UPDATE", 1 | 16 | 64}, - {"trg_before_insert", "INSERT", 2 | 4}, - {"trg_after_update", "UPDATE", 16} - } - ))); - - List triggers = agent.listTriggers("public", "app_table"); - - Assertions.assertEquals(3, triggers.size()); - Assertions.assertEquals("INSTEAD OF", triggers.get(0).getTiming()); - Assertions.assertEquals("BEFORE", triggers.get(1).getTiming()); - Assertions.assertEquals("AFTER", triggers.get(2).getTiming()); - Assertions.assertTrue(sql.get(0).contains("tg.tgtype AS trigger_type"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_trigger"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("sys_catalog.sys_class"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("sys_catalog.sys_namespace"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("NOT tg.tgisinternal"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("pg_catalog"), sql.get(0)); - } - - @Test - void postgresCompatListTriggersUsesPgCatalogTrigger() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - Connection connection = postgresCatalogConnection(sql, resultSet( - new String[]{"trigger_name", "event_manipulation", "action_timing"}, - new Object[][]{{"trg_insert", "INSERT", "AFTER"}} - )); - - Method afterConnect = KingbaseAgent.class.getDeclaredMethod("afterConnect", ConnectParams.class, Connection.class); - afterConnect.setAccessible(true); - afterConnect.invoke(agent, new ConnectParams(), connection); - TestSupport.setPrivateConnection(agent, connection); - - List triggers = agent.listTriggers("public", "app_table"); - - Assertions.assertEquals(1, triggers.size()); - Assertions.assertEquals("trg_insert", triggers.get(0).getName()); - // Verify the last SQL statement uses pg_catalog.pg_trigger - String lastSql = sql.get(sql.size() - 1); - Assertions.assertTrue(lastSql.contains("FROM pg_catalog.pg_trigger"), lastSql); - } - - @Test - void constrainedRegularTableMetadataPushesFilterTypesAndPaging() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{} - ))); - - agent.listTables("public", new MetadataListConstraints("ord", 30, 60, List.of("TABLE", "VIEW"))); - - 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)); - Assertions.assertFalse(sql.get(0).contains("relkind"), sql.get(0)); - Assertions.assertTrue(sql.get(0).endsWith("LIMIT 30 OFFSET 60"), sql.get(0)); - } - - @Test - void constrainedRegularMaterializedViewMetadataUsesMatviewCatalog() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{{"mv_sales", "MATERIALIZED_VIEW", "cached sales"}} - ))); - - List tables = agent.listTables("public", new MetadataListConstraints("sales", 10, null, List.of("MATERIALIZED_VIEW"))); - - Assertions.assertEquals(1, tables.size()); - Assertions.assertEquals("mv_sales", tables.get(0).getName()); - Assertions.assertEquals("MATERIALIZED_VIEW", tables.get(0).getTable_type()); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_matviews"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("FROM sys_catalog.sys_views"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("UPPER(CAST(mv.matviewname AS varchar(256))) LIKE ? ESCAPE '\\\\'"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("relkind"), sql.get(0)); - } - - @Test - void constrainedRegularObjectMetadataPushesRoutineTypesAndPaging() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"object_name", "object_type", "object_comment"}, - new Object[][]{} - ))); - - agent.listObjects("public", new MetadataListConstraints("sync", 10, null, List.of("PROCEDURE", "FUNCTION"))); - - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_proc"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("p.prorettype = 2278"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("prokind"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("ORDER BY CASE object_type"), sql.get(0)); - Assertions.assertTrue(sql.get(0).endsWith("LIMIT 10"), sql.get(0)); - } - - @Test - void constrainedMysqlCompatTableMetadataPushesInformationSchemaPaging() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"table_name", "table_type", "table_comment"}, - new Object[][]{} - ))); - - agent.listTables("PUBLIC", new MetadataListConstraints("ord", 20, 40, List.of("VIEW"))); - - Assertions.assertTrue(sql.get(0).contains("FROM information_schema.tables"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("table_type IN (?)"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("UPPER(t.table_name) LIKE ? ESCAPE '\\\\'"), sql.get(0)); - Assertions.assertTrue(sql.get(0).endsWith("LIMIT 20 OFFSET 40"), sql.get(0)); - } - - @Test - void regularRoutineSourceUsesKingbaseFunctionDefinition() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"source"}, - new Object[][]{{"CREATE FUNCTION public.format_name() RETURNS text AS $$ SELECT 'x'; $$"}} - ))); - - ObjectSource source = agent.getObjectSource("public", "format_name", "FUNCTION"); - - Assertions.assertTrue(source.getSource().startsWith("CREATE FUNCTION public.format_name()"), source.getSource()); - Assertions.assertTrue(sql.get(0).contains("SELECT sys_get_functiondef(p.oid) AS source"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_proc"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("prokind"), sql.get(0)); - } - - @Test - void regularGetColumnsUsesFormattedCatalogTypes() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, - resultSet( - new String[]{"column_name"}, - new Object[][]{{"id"}} - ), - resultSet( - new String[]{ - "column_name", - "data_type", - "is_nullable", - "column_default", - "column_comment", - "numeric_precision", - "numeric_scale", - "character_maximum_length" - }, - new Object[][]{ - {"id", "integer", false, "nextval('orders_id_seq'::regclass)", "identifier", 32, 0, null}, - {"create_time", "timestamp with time zone", true, null, null, null, null, null}, - {"name", "character varying(64)", true, null, "display name", null, null, 64} - } - ) - )); - - List columns = agent.getColumns("public", "orders"); - - Assertions.assertEquals(3, columns.size()); - Assertions.assertEquals("integer", columns.get(0).getData_type()); - Assertions.assertTrue(columns.get(0).getIs_primary_key()); - Assertions.assertFalse(columns.get(0).getIs_nullable()); - Assertions.assertEquals("timestamp with time zone", columns.get(1).getData_type()); - Assertions.assertNotEquals("USER-DEFINED", columns.get(1).getData_type()); - Assertions.assertEquals(Integer.valueOf(64), columns.get(2).getCharacter_maximum_length()); - Assertions.assertTrue(sql.get(1).contains("format_type(a.atttypid, a.atttypmod) AS data_type"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("FROM sys_catalog.sys_attribute"), sql.get(1)); - Assertions.assertFalse(sql.get(1).contains("information_schema.columns"), sql.get(1)); - Assertions.assertNull(columns.get(0).getExtra()); - } - - @Test - void regularGetColumnsFallsBackToPgGetExprAndCachesTheChoice() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, defaultExpressionFallbackConnection(sql)); - - List first = agent.getColumns("public", "orders"); - List second = agent.getColumns("public", "orders"); - - Assertions.assertEquals("nextval('orders_id_seq'::regclass)", first.get(0).getColumn_default()); - Assertions.assertEquals("nextval('orders_id_seq'::regclass)", second.get(0).getColumn_default()); - Assertions.assertEquals(1, sql.stream().filter(query -> query.contains("sys_get_expr(")).count(), sql.toString()); - Assertions.assertEquals(2, sql.stream().filter(query -> query.contains("pg_get_expr(")).count(), sql.toString()); - } - - @Test - void mysqlCompatGetColumnsRestoresBoundedCatalogCharacterTypes() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, - resultSet( - new String[]{"column_name"}, - new Object[][]{{"id"}} - ), - resultSet( - new String[]{ - "column_name", - "data_type", - "is_nullable", - "column_default", - "numeric_precision", - "numeric_scale", - "character_maximum_length", - "catalog_data_type", - "column_comment" - }, - new Object[][]{ - {"id", "int", "NO", null, 32, 0, null, "integer", "identifier"}, - {"name", "varchar", "YES", null, null, null, -1, "character varying(64)", null}, - {"notes", "varchar", "YES", null, null, null, -1, "varchar", null} - } - ) - )); - - List columns = agent.getColumns("PUBLIC", "orders"); - - Assertions.assertEquals("int", columns.get(0).getData_type()); - Assertions.assertEquals("identifier", columns.get(0).getComment()); - Assertions.assertEquals("character varying(64)", columns.get(1).getData_type()); - Assertions.assertEquals(Integer.valueOf(64), columns.get(1).getCharacter_maximum_length()); - Assertions.assertEquals("varchar", columns.get(2).getData_type()); - Assertions.assertEquals(Integer.valueOf(-1), columns.get(2).getCharacter_maximum_length()); - Assertions.assertTrue(sql.get(1).contains("FROM information_schema.columns"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("format_type(a.atttypid, a.atttypmod) AS catalog_data_type"), sql.get(1)); - Assertions.assertTrue(sql.get(1).contains("LEFT JOIN sys_catalog.sys_description"), sql.get(1)); - Assertions.assertNull(columns.get(0).getExtra()); - - String ddl = DdlBuilder.buildTableDdl( - "PUBLIC", - "orders", - columns, - Collections.emptyList(), - Collections.emptyList(), - true - ); - Assertions.assertTrue(ddl.contains("`name` character varying(64)"), ddl); - Assertions.assertTrue(ddl.contains("`notes` varchar"), ddl); - Assertions.assertFalse(ddl.contains("varchar(-1)"), ddl); - } - - @Test - void sqlServerCompatGetColumnsPreservesIdentityMetadata() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - setSqlServerIdentityCatalogMode(agent, true); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, - resultSet(new String[]{"column_name"}, new Object[][]{{"id"}}), - resultSet( - new String[]{ - "column_name", - "data_type", - "is_nullable", - "column_default", - "column_comment", - "numeric_precision", - "numeric_scale", - "character_maximum_length" - }, - new Object[][]{ - {"id", "int", false, null, null, 32, 0, null}, - {"name", "character varying", true, null, null, null, null, 64} - } - ), - resultSet( - new String[]{"column_name", "identity_seed", "identity_increment"}, - new Object[][]{{"id", "1", "1"}} - ) - )); - - List columns = agent.getColumns("dbo", "orders"); - - Assertions.assertEquals("identity(1,1)", columns.get(0).getExtra()); - Assertions.assertNull(columns.get(1).getExtra()); - Assertions.assertFalse(sql.get(1).contains("sys.identity_columns"), sql.get(1)); - Assertions.assertTrue(sql.get(2).contains("FROM sys.identity_columns"), sql.get(2)); - } - - @Test - void sqlServerCompatGetColumnsIgnoresBrokenIdentityCatalog() throws Exception { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - setSqlServerIdentityCatalogMode(agent, true); - TestSupport.setPrivateConnection(agent, sqlServerIdentityFailureConnection(sql)); - - List columns = agent.getColumns("dbo", "orders"); - - Assertions.assertEquals(2, columns.size()); - Assertions.assertEquals("id", columns.get(0).getName()); - Assertions.assertTrue(columns.get(0).getIs_primary_key()); - Assertions.assertNull(columns.get(0).getExtra()); - Assertions.assertFalse(sql.get(1).contains("sys.identity_columns"), sql.get(1)); - Assertions.assertTrue(sql.get(2).contains("FROM sys.identity_columns"), sql.get(2)); - Assertions.assertFalse(isSqlServerIdentityCatalogMode(agent)); - } - - @Test - void ddlRendersOnlyWellFormedSqlServerIdentityMetadata() { - ColumnInfo identity = new ColumnInfo("id", "int", false, null, true, "identity(1,1)", null, null, null, null); - ColumnInfo ordinary = new ColumnInfo("name", "varchar", true, null, false, "unknown metadata", null, null, null, 64); - - String ddl = DdlBuilder.buildTableDdl( - "dbo", - "orders", - Arrays.asList(identity, ordinary), - Collections.emptyList(), - Collections.emptyList() - ); - - Assertions.assertTrue(ddl.contains("\"id\" int IDENTITY(1,1) NOT NULL"), ddl); - Assertions.assertTrue(ddl.contains("\"name\" varchar(64)"), ddl); - Assertions.assertFalse(ddl.contains("unknown metadata"), ddl); - } - - @Test - void ddlOmitsUnknownCharacterLengthSentinel() { - ColumnInfo unlimited = new ColumnInfo("display_name", "varchar", true, null, false, null, null, null, null, -1); - - String ddl = DdlBuilder.buildTableDdl( - "public", - "accounts", - Collections.singletonList(unlimited), - Collections.emptyList(), - Collections.emptyList(), - true - ); - - Assertions.assertTrue(ddl.contains("`display_name` varchar"), ddl); - Assertions.assertFalse(ddl.contains("varchar(-1)"), ddl); - } - - @Test - void regularListIndexesIncludesPrimaryUniqueAndSecondaryIndexes() { - List sql = new ArrayList<>(); - KingbaseAgent agent = new KingbaseAgent(); - TestSupport.setPrivateConnection(agent, preparedConnection(sql, resultSet( - new String[]{"index_name", "index_type", "is_unique", "is_primary", "column_name", "ordinal_position"}, - new Object[][]{ - {"orders_pkey", "btree", true, true, "id", 1}, - {"idx_orders_created", "btree", false, false, "created", 1}, - {"idx_orders_name_created", "btree", false, false, "name", 1}, - {"idx_orders_name_created", "btree", false, false, "created", 2} - } - ))); - - List indexes = agent.listIndexes("public", "orders"); - - Assertions.assertEquals(3, indexes.size()); - Assertions.assertEquals("orders_pkey", indexes.get(0).getName()); - Assertions.assertEquals(Arrays.asList("id"), indexes.get(0).getColumns()); - Assertions.assertTrue(indexes.get(0).getIs_unique()); - Assertions.assertTrue(indexes.get(0).getIs_primary()); - Assertions.assertEquals("idx_orders_created", indexes.get(1).getName()); - Assertions.assertEquals(Arrays.asList("created"), indexes.get(1).getColumns()); - Assertions.assertFalse(indexes.get(1).getIs_unique()); - Assertions.assertFalse(indexes.get(1).getIs_primary()); - Assertions.assertEquals(Arrays.asList("name", "created"), indexes.get(2).getColumns()); - Assertions.assertTrue(sql.get(0).contains("FROM SYS_CATALOG.SYS_INDEX"), sql.get(0)); - Assertions.assertTrue(sql.get(0).contains("unnest(ix.indkey) WITH ORDINALITY"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("[pos.n]"), sql.get(0)); - Assertions.assertFalse(sql.get(0).contains("information_schema.table_constraints"), sql.get(0)); - } - - @Test - void mysqlCompatTimestampTypeNameIsReadAsTimestampText() throws Exception { - Timestamp timestamp = Timestamp.valueOf("2026-06-22 11:29:00"); - KingbaseAgent agent = new KingbaseAgent(); - agent.setMysqlCompatMode(true); - - Object value = readResultValue(agent, timestampResultSet(timestamp), Types.BINARY, "timestamp"); - - Assertions.assertEquals("2026-06-22 11:29:00.0", value); - } - - private static Connection preparedConnection(List sql, ResultSet rs) { - return preparedConnection(sql, new ResultSet[]{rs}); - } - - private static Connection preparedConnection(List sql, ResultSet... resultSets) { - int[] resultSetIndex = {0}; - PreparedStatement statement = proxy(PreparedStatement.class, (method, args) -> { - if ("executeQuery".equals(method.getName())) { - int current = Math.min(resultSetIndex[0], resultSets.length - 1); - resultSetIndex[0] += 1; - return resultSets[current]; - } - if ("setString".equals(method.getName())) { - return null; - } - if ("close".equals(method.getName())) { - return null; - } - return defaultValue(method.getReturnType()); - }); - Statement plainStatement = proxy(Statement.class, (method, args) -> { - if ("executeQuery".equals(method.getName())) { - sql.add(String.valueOf(args[0])); - int current = Math.min(resultSetIndex[0], resultSets.length - 1); - resultSetIndex[0] += 1; - return resultSets[current]; - } - if ("close".equals(method.getName())) { - return null; - } - return defaultValue(method.getReturnType()); - }); - return proxy(Connection.class, (method, args) -> { - if ("prepareStatement".equals(method.getName())) { - sql.add(String.valueOf(args[0])); - return statement; - } - if ("createStatement".equals(method.getName())) { - return plainStatement; - } - if ("isClosed".equals(method.getName())) { - return false; - } - return defaultValue(method.getReturnType()); - }); - } - - private static Connection compositeAwareTableConnection(List 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 sql, String failingSqlFragment, ResultSet fallback) { - return preparedConnectionWithFailures(sql, List.of(failingSqlFragment), fallback); - } - - private static Connection sqlServerIdentityFailureConnection(List sql) { - 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("FROM sys.identity_columns")) { - throw new SQLException("ERROR: cannot open file base/14465/t48_3852767: No such file or directory"); - } - if (query.contains("information_schema.table_constraints")) { - return resultSet(new String[]{"column_name"}, new Object[][]{{"id"}}); - } - return resultSet( - new String[]{ - "column_name", - "data_type", - "is_nullable", - "column_default", - "column_comment", - "numeric_precision", - "numeric_scale", - "character_maximum_length" - }, - new Object[][]{ - {"id", "int", false, null, null, 32, 0, null}, - {"name", "character varying", false, null, null, null, null, 64} - } - ); - } - if ("close".equals(statementMethod.getName())) return null; - return defaultValue(statementMethod.getReturnType()); - }); - } - if ("isClosed".equals(method.getName())) return false; - return defaultValue(method.getReturnType()); - }); - } - - private static Connection defaultExpressionFallbackConnection(List sql) { - return proxy(Connection.class, (method, args) -> { - 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 resultSet(new String[]{"column_name"}, new Object[][]{{"id"}}); - } - if ("close".equals(statementMethod.getName())) return null; - return defaultValue(statementMethod.getReturnType()); - }); - } - 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_get_expr(")) { - throw new SQLException( - "ERROR: function sys_get_expr(pg_node_tree, oid) does not exist", - "42883" - ); - } - return resultSet( - new String[]{ - "column_name", - "data_type", - "is_nullable", - "column_default", - "column_comment", - "numeric_precision", - "numeric_scale", - "character_maximum_length" - }, - new Object[][]{ - {"id", "integer", false, "nextval('orders_id_seq'::regclass)", null, 32, 0, null} - } - ); - } - if ("close".equals(statementMethod.getName())) return null; - return defaultValue(statementMethod.getReturnType()); - }); - } - if ("isClosed".equals(method.getName())) return false; - return defaultValue(method.getReturnType()); - }); - } - - private static Connection preparedConnectionWithMetadataFailure( - List sql, - String message, - String sqlState, - ResultSet fallback - ) { - return proxy(Connection.class, (method, args) -> { - if ("prepareStatement".equals(method.getName())) { - String preparedSql = String.valueOf(args[0]); - sql.add(preparedSql); - return proxy(PreparedStatement.class, (statementMethod, statementArgs) -> { - if ("executeQuery".equals(statementMethod.getName())) { - if (preparedSql.contains("information_schema.tables")) { - throw new SQLException(message, sqlState); - } - return fallback; - } - if ("close".equals(statementMethod.getName())) { - return null; - } - return defaultValue(statementMethod.getReturnType()); - }); - } - if ("isClosed".equals(method.getName())) { - return false; - } - return defaultValue(method.getReturnType()); - }); - } - - private static Connection preparedConnectionWithFailures(List sql, List failingSqlFragments, ResultSet fallback) { - return proxy(Connection.class, (method, args) -> { - if ("prepareStatement".equals(method.getName())) { - String preparedSql = String.valueOf(args[0]); - sql.add(preparedSql); - return proxy(PreparedStatement.class, (statementMethod, statementArgs) -> { - if ("executeQuery".equals(statementMethod.getName())) { - for (String failingSqlFragment : failingSqlFragments) { - if (preparedSql.contains(failingSqlFragment)) { - throw new SQLException("relation does not exist: " + failingSqlFragment); - } - } - return fallback; - } - if ("close".equals(statementMethod.getName())) { - return null; - } - return defaultValue(statementMethod.getReturnType()); - }); - } - if ("isClosed".equals(method.getName())) { - return false; - } - return defaultValue(method.getReturnType()); - }); - } - - private static Connection postgresCatalogConnection(List 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 Connection compatibilityModeConnection( - List sql, - String databaseMode, - boolean sqlModeExists - ) { - 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("LOWER(name) = 'database_mode'")) { - return databaseMode == null - ? resultSet(new String[]{"setting"}, new Object[][]{}) - : resultSet(new String[]{"setting"}, new Object[][]{{databaseMode}}); - } - if (query.contains("LOWER(name) = 'sql_mode'") && sqlModeExists) { - return resultSet(new String[]{"probe"}, new Object[][]{{1}}); - } - return resultSet(new String[]{"probe"}, new Object[][]{}); - } - 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) -> { - switch (method.getName()) { - case "next": - index[0] += 1; - return index[0] < rows.length; - case "getString": - Object key = args[0]; - if (key instanceof Number) { - return rows[index[0]][((Number) key).intValue() - 1]; - } - for (int i = 0; i < columns.length; i++) { - if (columns[i].equalsIgnoreCase(String.valueOf(key))) { - return rows[index[0]][i]; - } - } - return null; - case "getBoolean": - Object booleanValue = columnValue(columns, rows[index[0]], args[0]); - if (booleanValue instanceof Boolean) return booleanValue; - if (booleanValue instanceof Number) return ((Number) booleanValue).intValue() != 0; - return Boolean.parseBoolean(String.valueOf(booleanValue)); - case "getInt": - Object intValue = columnValue(columns, rows[index[0]], args[0]); - if (intValue instanceof Number) return ((Number) intValue).intValue(); - return Integer.parseInt(String.valueOf(intValue)); - case "getObject": - return columnValue(columns, rows[index[0]], args[0]); - case "wasNull": - return false; - case "close": - return null; - default: - return defaultValue(method.getReturnType()); - } - }); - } - - private static Object columnValue(String[] columns, Object[] row, Object key) { - if (key instanceof Number) { - return row[((Number) key).intValue() - 1]; - } - for (int i = 0; i < columns.length; i++) { - if (columns[i].equalsIgnoreCase(String.valueOf(key))) { - return row[i]; - } - } - return null; - } - - private static ResultSet timestampResultSet(Timestamp timestamp) { - return proxy(ResultSet.class, (method, args) -> { - switch (method.getName()) { - case "getTimestamp": - return timestamp; - case "getBytes": - throw new AssertionError("timestamp should not be read as bytes"); - case "wasNull": - return false; - default: - return defaultValue(method.getReturnType()); - } - }); - } - - private static T proxy(Class type, MethodHandler handler) { - InvocationHandler invocationHandler = (Object unused, Method method, Object[] args) -> handler.handle(method, args); - return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, invocationHandler)); - } - - private static Object defaultValue(Class type) { - if (Boolean.TYPE.equals(type)) return false; - if (Byte.TYPE.equals(type)) return (byte) 0; - if (Short.TYPE.equals(type)) return (short) 0; - if (Integer.TYPE.equals(type)) return 0; - if (Long.TYPE.equals(type)) return 0L; - if (Float.TYPE.equals(type)) return 0f; - if (Double.TYPE.equals(type)) return 0.0d; - if (Character.TYPE.equals(type)) return '\0'; - return null; - } - - private interface MethodHandler { - Object handle(Method method, Object[] args) throws Throwable; - } - - private static Object readResultValue(KingbaseAgent agent, ResultSet rs, int sqlType, String columnTypeName) throws Exception { - Method method = KingbaseAgent.class.getDeclaredMethod("resultValue", ResultSet.class, int.class, int.class, String.class); - method.setAccessible(true); - return method.invoke(agent, rs, 1, sqlType, columnTypeName); - } - - private static void setSqlServerIdentityCatalogMode(KingbaseAgent agent, boolean enabled) throws Exception { - java.lang.reflect.Field field = KingbaseAgent.class.getDeclaredField("sqlServerIdentityCatalogMode"); - field.setAccessible(true); - field.setBoolean(agent, enabled); - } - - private static boolean isSqlServerIdentityCatalogMode(KingbaseAgent agent) throws Exception { - java.lang.reflect.Field field = KingbaseAgent.class.getDeclaredField("sqlServerIdentityCatalogMode"); - field.setAccessible(true); - return field.getBoolean(agent); - } -} diff --git a/agents/scripts/build_offline_zip.sh b/agents/scripts/build_offline_zip.sh index 1e7495f8d..90393240d 100755 --- a/agents/scripts/build_offline_zip.sh +++ b/agents/scripts/build_offline_zip.sh @@ -47,10 +47,6 @@ for platform in "${PLATFORMS[@]}"; do # Copy all driver JARs (platform-independent) for jar_file in "$RELEASE_DIR"/dbx-agent-*.jar; do [ -f "$jar_file" ] || continue - # Kingbase is distributed only as a native agent; keep legacy JDBC builds out of offline bundles. - case "$(basename "$jar_file")" in - dbx-agent-kingbase.jar|dbx-agent-kingbase-*.jar) continue ;; - esac cp "$jar_file" "$WORK/drivers/" done diff --git a/agents/settings.gradle b/agents/settings.gradle index c91774f88..2337a9a9b 100644 --- a/agents/settings.gradle +++ b/agents/settings.gradle @@ -2,7 +2,7 @@ rootProject.name = 'dbx-agents' def infrastructureModules = ['common', 'test-support'] def driverModules = [ - 'access', 'dameng', 'kingbase', 'vastbase', 'goldendb', 'databend', 'databricks', 'saphana', + 'access', 'dameng', 'vastbase', 'goldendb', 'databend', 'databricks', 'saphana', 'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s', 'bigquery', 'kylin', 'sundb', 'h2', 'h2-legacy', 'snowflake', 'trino', 'hive', 'spark', 'db2', 'informix', 'neo4j', 'cassandra', 'mongodb', 'highgo', 'tdengine', 'yashandb', 'oscar',