fix(kingbase): 修复连接与元数据查询异常

This commit is contained in:
zipg authored and GitHub committed 2026-07-07 22:12:36 +08:00
1 parent 63ff9321a4
commit 6cba08fccb
13 files changed
+369 -127

No files matched your search

@@ -30,6 +30,21 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
public final class KingbaseAgent extends PostgresLikeAgent { public final class KingbaseAgent extends PostgresLikeAgent {
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_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))";
public static final PostgresLikeAgentProfile KINGBASE_PROFILE = new PostgresLikeAgentProfile( public static final PostgresLikeAgentProfile KINGBASE_PROFILE = new PostgresLikeAgentProfile(
"com.kingbase8.Driver", "com.kingbase8.Driver",
"jdbc:kingbase8://{host}:{port}/{database}" "jdbc:kingbase8://{host}:{port}/{database}"
@@ -110,36 +125,7 @@ public final class KingbaseAgent extends PostgresLikeAgent {
if (isMysqlCompatMode()) { if (isMysqlCompatMode()) {
return listTables(schema, "table_type IN ('BASE TABLE', 'VIEW')"); return listTables(schema, "table_type IN ('BASE TABLE', 'VIEW')");
} }
return unchecked(() -> { return queryRegularTables(schema, MetadataListConstraints.NONE);
List<TableInfo> result = new ArrayList<>();
String sql = "SELECT c.relname AS table_name, " +
"CASE c.relkind " +
"WHEN 'r' THEN 'TABLE' " +
"WHEN 'p' THEN 'TABLE' " +
"WHEN 'v' THEN 'VIEW' " +
"WHEN 'm' THEN 'MATERIALIZED_VIEW' " +
"WHEN 'f' THEN 'FOREIGN_TABLE' " +
"ELSE 'TABLE' END AS table_type, " +
"d.description AS table_comment " +
"FROM sys_catalog.sys_class c " +
"JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace " +
"LEFT JOIN sys_catalog.sys_description d ON d.objoid = c.oid AND d.objsubid = 0 " +
"WHERE n.nspname = ? AND c.relkind IN ('r','p','v','m','f') " +
"ORDER BY c.relname";
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
stmt.setString(1, effectiveSchema(schema));
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 result;
});
} }
@Override @Override
@@ -164,23 +150,14 @@ public final class KingbaseAgent extends PostgresLikeAgent {
return unchecked(() -> { return unchecked(() -> {
List<TableInfo> result = new ArrayList<>(); List<TableInfo> result = new ArrayList<>();
List<Object> args = new ArrayList<>(); List<Object> args = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT c.relname AS table_name, ") List<String> branches = new ArrayList<>();
.append("CASE c.relkind ") addRegularRelationBranches(branches, args, effectiveSchema(schema), constraints, "table_name", "table_type", "table_comment");
.append("WHEN 'r' THEN 'TABLE' ") if (branches.isEmpty()) {
.append("WHEN 'p' THEN 'TABLE' ") return List.of();
.append("WHEN 'v' THEN 'VIEW' ") }
.append("WHEN 'm' THEN 'MATERIALIZED_VIEW' ") StringBuilder sql = new StringBuilder("SELECT table_name, table_type, table_comment FROM (")
.append("WHEN 'f' THEN 'FOREIGN_TABLE' ") .append(String.join(" UNION ALL ", branches))
.append("ELSE 'TABLE' END AS table_type, ") .append(") metadata_tables ORDER BY table_name");
.append("d.description AS table_comment ")
.append("FROM sys_catalog.sys_class c ")
.append("JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace ")
.append("LEFT JOIN sys_catalog.sys_description d ON d.objoid = c.oid AND d.objsubid = 0 ")
.append("WHERE n.nspname = ? AND c.relkind IN ('r','p','v','m','f')");
args.add(effectiveSchema(schema));
appendRelkindPredicate(sql, args, constraints);
MetadataSqlSupport.appendNameFilter(sql, args, "c.relname", constraints);
sql.append(" ORDER BY c.relname");
MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints); MetadataSqlSupport.appendLiteralLimitOffset(sql, constraints);
try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) { try (PreparedStatement stmt = requireConnected().prepareStatement(sql.toString())) {
MetadataSqlSupport.bind(stmt, args); MetadataSqlSupport.bind(stmt, args);
@@ -232,16 +209,15 @@ public final class KingbaseAgent extends PostgresLikeAgent {
return result; return result;
} }
String sql = "SELECT p.proname AS routine_name, " + String sql = "SELECT " + KINGBASE_ROUTINE_NAME + " AS routine_name, " +
"CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type, " + "CASE WHEN p.prorettype = " + KINGBASE_VOID_TYPE_OID + " THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type, " +
"d.description AS routine_comment " + KINGBASE_DESCRIPTION + " AS routine_comment " +
"FROM sys_catalog.sys_proc p " + "FROM sys_catalog.sys_proc p " +
"JOIN sys_catalog.sys_namespace n ON n.oid = p.pronamespace " + "JOIN sys_catalog.sys_namespace n ON " + KINGBASE_NAMESPACE_OID + " = " + KINGBASE_ROUTINE_NAMESPACE + " " +
"LEFT JOIN sys_catalog.sys_description d ON d.objoid = p.oid AND d.objsubid = 0 " + "LEFT JOIN sys_catalog.sys_description d ON CAST(d.objoid AS varchar(64)) = " + KINGBASE_ROUTINE_OID + " AND d.objsubid = 0 " +
"WHERE n.nspname = ? AND p.prokind IN ('p','f') " + "WHERE " + KINGBASE_SCHEMA_NAME + " = " + sqlString(effectiveSchema) + " " +
"ORDER BY p.proname"; "ORDER BY " + KINGBASE_ROUTINE_NAME;
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
stmt.setString(1, effectiveSchema);
try (ResultSet rs = stmt.executeQuery()) { try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) { while (rs.next()) {
result.add(new ObjectInfo( result.add(new ObjectInfo(
@@ -282,29 +258,19 @@ public final class KingbaseAgent extends PostgresLikeAgent {
List<String> branches = new ArrayList<>(); List<String> branches = new ArrayList<>();
List<Object> args = new ArrayList<>(); List<Object> args = new ArrayList<>();
if (constraints.includesTableLikeTypes()) { if (constraints.includesTableLikeTypes()) {
StringBuilder tableSql = new StringBuilder("SELECT c.relname AS object_name, ") addRegularRelationBranches(branches, args, effectiveSchema, constraints, "object_name", "object_type", "object_comment");
.append("CASE c.relkind WHEN 'r' THEN 'TABLE' WHEN 'p' THEN 'TABLE' WHEN 'v' THEN 'VIEW' WHEN 'm' THEN 'MATERIALIZED_VIEW' WHEN 'f' THEN 'FOREIGN_TABLE' ELSE 'TABLE' END AS object_type, ")
.append("d.description AS object_comment ")
.append("FROM sys_catalog.sys_class c ")
.append("JOIN sys_catalog.sys_namespace n ON n.oid = c.relnamespace ")
.append("LEFT JOIN sys_catalog.sys_description d ON d.objoid = c.oid AND d.objsubid = 0 ")
.append("WHERE n.nspname = ? AND c.relkind IN ('r','p','v','m','f')");
args.add(effectiveSchema);
appendRelkindPredicate(tableSql, args, constraints);
MetadataSqlSupport.appendNameFilter(tableSql, args, "c.relname", constraints);
branches.add(tableSql.toString());
} }
if (constraints.objectTypeAllowed("PROCEDURE") || constraints.objectTypeAllowed("FUNCTION")) { if (constraints.objectTypeAllowed("PROCEDURE") || constraints.objectTypeAllowed("FUNCTION")) {
StringBuilder routineSql = new StringBuilder("SELECT p.proname AS object_name, ") StringBuilder routineSql = new StringBuilder("SELECT ")
.append("CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS object_type, ") .append(KINGBASE_ROUTINE_NAME).append(" AS object_name, ")
.append("d.description AS object_comment ") .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("FROM sys_catalog.sys_proc p ")
.append("JOIN sys_catalog.sys_namespace n ON n.oid = p.pronamespace ") .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 d.objoid = p.oid AND d.objsubid = 0 ") .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 n.nspname = ?"); .append("WHERE ").append(KINGBASE_SCHEMA_NAME).append(" = ").append(sqlString(effectiveSchema));
args.add(effectiveSchema);
appendRoutineKindPredicate(routineSql, args, constraints); appendRoutineKindPredicate(routineSql, args, constraints);
MetadataSqlSupport.appendNameFilter(routineSql, args, "p.proname", constraints); MetadataSqlSupport.appendNameFilter(routineSql, args, KINGBASE_ROUTINE_NAME, constraints);
branches.add(routineSql.toString()); branches.add(routineSql.toString());
} }
if (branches.isEmpty()) { if (branches.isEmpty()) {
@@ -345,6 +311,14 @@ public final class KingbaseAgent extends PostgresLikeAgent {
"FROM information_schema.views " + "FROM information_schema.views " +
"WHERE table_schema = " + sqlString(effectiveSchema(schema)) + "WHERE table_schema = " + sqlString(effectiveSchema(schema)) +
" AND table_name = " + sqlString(name); " 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 (Statement stmt = requireConnected().createStatement()) {
try (ResultSet rs = stmt.executeQuery(sql)) { try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) { if (rs.next()) {
@@ -359,16 +333,15 @@ public final class KingbaseAgent extends PostgresLikeAgent {
private ObjectSource routineSource(String schema, String name, String objectType) { private ObjectSource routineSource(String schema, String name, String objectType) {
return unchecked(() -> { return unchecked(() -> {
String source = ""; String source = "";
String prokind = "PROCEDURE".equalsIgnoreCase(objectType) ? "p" : "f";
String sql = "SELECT sys_get_functiondef(p.oid) AS source " + String sql = "SELECT sys_get_functiondef(p.oid) AS source " +
"FROM sys_catalog.sys_proc p " + "FROM sys_catalog.sys_proc p " +
"JOIN sys_catalog.sys_namespace n ON n.oid = p.pronamespace " + "JOIN sys_catalog.sys_namespace n ON " + KINGBASE_NAMESPACE_OID + " = " + KINGBASE_ROUTINE_NAMESPACE + " " +
"WHERE n.nspname = ? AND p.proname = ? AND p.prokind = ? " + "WHERE " + KINGBASE_SCHEMA_NAME + " = " + sqlString(effectiveSchema(schema)) +
"ORDER BY p.oid LIMIT 1"; " 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 (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
stmt.setString(1, effectiveSchema(schema));
stmt.setString(2, name);
stmt.setString(3, prokind);
try (ResultSet rs = stmt.executeQuery()) { try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) { if (rs.next()) {
source = coalesce(rs.getString("source")); source = coalesce(rs.getString("source"));
@@ -647,27 +620,103 @@ public final class KingbaseAgent extends PostgresLikeAgent {
|| constraints.objectTypeAllowed("FUNCTION"); || constraints.objectTypeAllowed("FUNCTION");
} }
private static void appendRelkindPredicate(StringBuilder sql, List<Object> args, MetadataListConstraints constraints) { private static void addRegularRelationBranches(
if (!constraints.hasObjectTypes()) { List<String> branches,
return; List<Object> args,
} String schema,
List<String> kinds = new ArrayList<>(); MetadataListConstraints constraints,
String nameAlias,
String typeAlias,
String commentAlias
) {
if (constraints.tableTypeAllowed("TABLE")) { if (constraints.tableTypeAllowed("TABLE")) {
kinds.add("r"); branches.add(regularRelationBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias, "TABLE"));
kinds.add("p");
} }
if (constraints.tableTypeAllowed("VIEW")) { if (constraints.tableTypeAllowed("VIEW")) {
kinds.add("v"); branches.add(regularViewBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias));
} }
if (constraints.tableTypeAllowed("MATERIALIZED_VIEW")) { if (constraints.tableTypeAllowed("MATERIALIZED_VIEW")) {
kinds.add("m"); branches.add(regularMaterializedViewBranch(schema, args, constraints, nameAlias, typeAlias, commentAlias));
} }
if (kinds.isEmpty()) { }
sql.append(" AND 1 = 0");
return; private static String regularRelationBranch(
} String schema,
sql.append(" AND c.relkind IN (").append(MetadataSqlSupport.placeholders(kinds.size())).append(")"); List<Object> args,
args.addAll(kinds); 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));
appendRegularTablePredicate(sql);
MetadataSqlSupport.appendNameFilter(sql, args, KINGBASE_REL_NAME, constraints);
return sql.toString();
}
private static String regularViewBranch(
String schema,
List<Object> 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<Object> 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 appendRegularTablePredicate(StringBuilder sql) {
sql.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_rewrite r ")
.append("WHERE CAST(r.ev_class AS varchar(64)) = ").append(KINGBASE_REL_OID)
.append(" AND CAST(r.rulename AS varchar(256)) = '_RETURN')")
.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_index ix ")
.append("WHERE CAST(ix.indexrelid AS varchar(64)) = ").append(KINGBASE_REL_OID).append(')')
.append(" AND NOT EXISTS (SELECT 1 FROM sys_catalog.sys_attribute sa1 ")
.append("JOIN sys_catalog.sys_attribute sa2 ON CAST(sa2.attrelid AS varchar(64)) = CAST(sa1.attrelid AS varchar(64)) ")
.append("WHERE CAST(sa1.attrelid AS varchar(64)) = ").append(KINGBASE_REL_OID)
.append(" AND CAST(sa1.attname AS varchar(256)) = 'last_value'")
.append(" AND CAST(sa2.attname AS varchar(256)) = 'log_cnt')");
} }
private static void appendMysqlCompatTableTypePredicate(StringBuilder sql, List<Object> args, MetadataListConstraints constraints) { private static void appendMysqlCompatTableTypePredicate(StringBuilder sql, List<Object> args, MetadataListConstraints constraints) {
@@ -691,19 +740,21 @@ public final class KingbaseAgent extends PostgresLikeAgent {
} }
private static void appendRoutineKindPredicate(StringBuilder sql, List<Object> args, MetadataListConstraints constraints) { private static void appendRoutineKindPredicate(StringBuilder sql, List<Object> args, MetadataListConstraints constraints) {
List<String> kinds = new ArrayList<>(); if (!constraints.hasObjectTypes()) {
if (!constraints.hasObjectTypes() || constraints.objectTypeAllowed("PROCEDURE")) { return;
kinds.add("p");
} }
if (!constraints.hasObjectTypes() || constraints.objectTypeAllowed("FUNCTION")) { boolean includeProcedures = constraints.objectTypeAllowed("PROCEDURE");
kinds.add("f"); boolean includeFunctions = constraints.objectTypeAllowed("FUNCTION");
if (includeProcedures && includeFunctions) {
return;
} }
if (kinds.isEmpty()) { if (!includeProcedures && !includeFunctions) {
sql.append(" AND 1 = 0"); sql.append(" AND 1 = 0");
return; return;
} }
sql.append(" AND p.prokind IN (").append(MetadataSqlSupport.placeholders(kinds.size())).append(")"); sql.append(includeProcedures
args.addAll(kinds); ? " AND p.prorettype = " + KINGBASE_VOID_TYPE_OID
: " AND p.prorettype <> " + KINGBASE_VOID_TYPE_OID);
} }
private static List<ObjectInfo> toObjects(List<TableInfo> tables, String schema) { private static List<ObjectInfo> toObjects(List<TableInfo> tables, String schema) {
@@ -147,7 +147,11 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertEquals("app_view", tables.get(1).getName()); Assertions.assertEquals("app_view", tables.get(1).getName());
Assertions.assertEquals("VIEW", tables.get(1).getTable_type()); Assertions.assertEquals("VIEW", tables.get(1).getTable_type());
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0)); Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("c.relkind IN ('r','p','v','m','f')"), sql.get(0)); Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_rewrite"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_index"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_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 @Test
@@ -177,7 +181,8 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertEquals("format_name", objects.get(3).getName()); Assertions.assertEquals("format_name", objects.get(3).getName());
Assertions.assertEquals("FUNCTION", objects.get(3).getObject_type()); 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("FROM sys_catalog.sys_proc"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("p.prokind IN ('p','f')"), 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 @Test
@@ -192,11 +197,33 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
agent.listTables("public", new MetadataListConstraints("ord", 30, 60, List.of("TABLE", "VIEW"))); agent.listTables("public", new MetadataListConstraints("ord", 30, 60, List.of("TABLE", "VIEW")));
Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0)); Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_class"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("c.relkind IN (?, ?, ?)"), sql.get(0)); Assertions.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_views"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("UPPER(c.relname) LIKE ? ESCAPE '\\\\'"), 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)); Assertions.assertTrue(sql.get(0).endsWith("LIMIT 30 OFFSET 60"), sql.get(0));
} }
@Test
void constrainedRegularMaterializedViewMetadataUsesMatviewCatalog() {
List<String> 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<TableInfo> 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 @Test
void constrainedRegularObjectMetadataPushesRoutineTypesAndPaging() { void constrainedRegularObjectMetadataPushesRoutineTypesAndPaging() {
List<String> sql = new ArrayList<>(); List<String> sql = new ArrayList<>();
@@ -209,7 +236,8 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
agent.listObjects("public", new MetadataListConstraints("sync", 10, null, List.of("PROCEDURE", "FUNCTION"))); 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("FROM sys_catalog.sys_proc"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("p.prokind IN (?, ?)"), 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).contains("ORDER BY CASE object_type"), sql.get(0));
Assertions.assertTrue(sql.get(0).endsWith("LIMIT 10"), sql.get(0)); Assertions.assertTrue(sql.get(0).endsWith("LIMIT 10"), sql.get(0));
} }
@@ -246,6 +274,7 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertTrue(source.getSource().startsWith("CREATE FUNCTION public.format_name()"), source.getSource()); 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("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.assertTrue(sql.get(0).contains("FROM sys_catalog.sys_proc"), sql.get(0));
Assertions.assertFalse(sql.get(0).contains("prokind"), sql.get(0));
} }
@Test @Test
@@ -1392,6 +1392,7 @@ const databaseLabel = computed(() => {
}); });
const databasePlaceholder = computed(() => { const databasePlaceholder = computed(() => {
if (form.value.db_type === "kingbase") return t("connection.databasePlaceholderRequired");
const fallback = defaultDatabaseForProfile(); const fallback = defaultDatabaseForProfile();
if (!fallback) return t("connection.databasePlaceholder"); if (!fallback) return t("connection.databasePlaceholder");
return t("connection.databasePlaceholderWithDefault", { database: fallback }); return t("connection.databasePlaceholderWithDefault", { database: fallback });
@@ -1430,7 +1431,7 @@ function defaultDatabaseForProfile() {
if (selectedType.value === "cockroachdb") return "defaultdb"; if (selectedType.value === "cockroachdb") return "defaultdb";
if (form.value.db_type === "highgo") return "highgo"; if (form.value.db_type === "highgo") return "highgo";
if (form.value.db_type === "yashandb") return "yasdb"; if (form.value.db_type === "yashandb") return "yasdb";
if (form.value.db_type === "postgres" || form.value.db_type === "kingbase" || form.value.db_type === "vastbase") return "postgres"; if (form.value.db_type === "postgres" || form.value.db_type === "vastbase") return "postgres";
if (form.value.db_type === "sqlserver") return "master"; if (form.value.db_type === "sqlserver") return "master";
if (form.value.db_type === "oracle") return "ORCL"; if (form.value.db_type === "oracle") return "ORCL";
if (form.value.db_type === "h2" && h2ConnectionMode.value === "tcp") return "test"; if (form.value.db_type === "h2" && h2ConnectionMode.value === "tcp") return "test";
@@ -1912,8 +1913,9 @@ async function testConnection() {
const runId = ++testRunId; const runId = ++testRunId;
isTesting.value = true; isTesting.value = true;
testResult.value = null; testResult.value = null;
const config = connectionConfigForSubmit(editingId.value || draftTestConnectionId.value); let config: ConnectionConfig | null = null;
try { try {
config = connectionConfigForSubmit(editingId.value || draftTestConnectionId.value);
await ensureRequiredAgentDriverInstalled(config); await ensureRequiredAgentDriverInstalled(config);
const msg = await testConnectionWithTimeout(config, runId); const msg = await testConnectionWithTimeout(config, runId);
if (runId !== testRunId) return; if (runId !== testRunId) return;
@@ -1924,10 +1926,11 @@ async function testConnection() {
clearEditedConnectionErrorAfterSuccessfulTest(); clearEditedConnectionErrorAfterSuccessfulTest();
} catch (e: any) { } catch (e: any) {
if (runId !== testRunId) return; if (runId !== testRunId) return;
const message = connectionErrorWithDriverUpdateHint(config, mongodbAuthFailureHint(String(e))); const rawMessage = mongodbAuthFailureHint(errorMessage(e));
const fallbackMessage = await tryNacosDockerConsoleFallback(config, message, runId); const message = config ? connectionErrorWithDriverUpdateHint(config, rawMessage) : rawMessage;
const fallbackMessage = config ? await tryNacosDockerConsoleFallback(config, message, runId) : null;
if (runId !== testRunId) return; if (runId !== testRunId) return;
const shouldShowSqlServerLegacyMode = !fallbackMessage && config.db_type === "sqlserver" && !isSqlServerLegacyUnencryptedMode(config.url_params) && isSqlServerTlsHandshakeFailure(message); const shouldShowSqlServerLegacyMode = !fallbackMessage && config?.db_type === "sqlserver" && !isSqlServerLegacyUnencryptedMode(config.url_params) && isSqlServerTlsHandshakeFailure(message);
if (shouldShowSqlServerLegacyMode) { if (shouldShowSqlServerLegacyMode) {
configTab.value = "advanced"; configTab.value = "advanced";
} }
@@ -2091,6 +2094,12 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
if (!config.name?.trim()) { if (!config.name?.trim()) {
config.name = generateConnectionName(); config.name = generateConnectionName();
} }
if (config.db_type === "kingbase") {
config.database = config.database?.trim() || undefined;
if (!config.database) {
throw new Error(t("connection.kingbaseDatabaseRequired"));
}
}
config.transport_layers = (config.transport_layers || []).map(normalizeTransportLayer); config.transport_layers = (config.transport_layers || []).map(normalizeTransportLayer);
config.transport_layers = config.transport_layers.map((layer) => { config.transport_layers = config.transport_layers.map((layer) => {
if (layer.type !== "ssh") return layer; if (layer.type !== "ssh") return layer;
@@ -2556,12 +2565,12 @@ async function preloadVisibleDatabaseNames() {
if (visibleDatabaseNames.value.length > 0) return; if (visibleDatabaseNames.value.length > 0) return;
isLoadingVisibleDatabases.value = true; isLoadingVisibleDatabases.value = true;
const draftId = buildDraftVisibleDatabasesConnectionId(uuid()); const draftId = buildDraftVisibleDatabasesConnectionId(uuid());
const draftConfig = {
...connectionConfigForSubmit(draftId),
id: draftId,
one_time: true,
};
try { try {
const draftConfig = {
...connectionConfigForSubmit(draftId),
id: draftId,
one_time: true,
};
await api.connectDb(draftConfig); await api.connectDb(draftConfig);
visibleDatabaseNames.value = await loadVisibleDatabaseNames(draftId, draftConfig); visibleDatabaseNames.value = await loadVisibleDatabaseNames(draftId, draftConfig);
} catch { } catch {
@@ -2580,13 +2589,13 @@ async function openVisibleDatabasesPicker() {
visibleDatabaseError.value = ""; visibleDatabaseError.value = "";
visibleDatabaseSearchText.value = ""; visibleDatabaseSearchText.value = "";
const draftId = buildDraftVisibleDatabasesConnectionId(uuid()); const draftId = buildDraftVisibleDatabasesConnectionId(uuid());
const draftConfig = {
...connectionConfigForSubmit(draftId),
id: draftId,
one_time: true,
};
try { try {
const draftConfig = {
...connectionConfigForSubmit(draftId),
id: draftId,
one_time: true,
};
await api.connectDb(draftConfig); await api.connectDb(draftConfig);
const names = await loadVisibleDatabaseNames(draftId, draftConfig); const names = await loadVisibleDatabaseNames(draftId, draftConfig);
visibleDatabaseNames.value = names; visibleDatabaseNames.value = names;
@@ -2598,8 +2607,9 @@ async function openVisibleDatabasesPicker() {
} catch (e: any) { } catch (e: any) {
visibleDatabaseNames.value = []; visibleDatabaseNames.value = [];
visibleDatabaseSelection.value = new Set(); visibleDatabaseSelection.value = new Set();
visibleDatabaseError.value = mongodbAuthFailureHint(String(e?.message || e)); visibleDatabaseError.value = mongodbAuthFailureHint(errorMessage(e));
testResult.value = { ok: false, message: visibleDatabaseError.value }; testResult.value = { ok: false, message: visibleDatabaseError.value };
showVisibleDatabasesDialog.value = true;
} finally { } finally {
await api.disconnectDb(draftId).catch(() => undefined); await api.disconnectDb(draftId).catch(() => undefined);
isLoadingVisibleDatabases.value = false; isLoadingVisibleDatabases.value = false;
+2
View File
@@ -180,7 +180,9 @@ export default {
password: "Password", password: "Password",
database: "Database", database: "Database",
databasePlaceholder: "Optional", databasePlaceholder: "Optional",
databasePlaceholderRequired: "Required, enter an existing database",
databasePlaceholderWithDefault: "Optional, defaults to {database}", databasePlaceholderWithDefault: "Optional, defaults to {database}",
kingbaseDatabaseRequired: "Kingbase requires an existing database before connecting or loading the database list.",
defaultDatabase: "Default DB", defaultDatabase: "Default DB",
authDatabase: "Auth DB", authDatabase: "Auth DB",
authDatabasePlaceholder: "Optional, often admin", authDatabasePlaceholder: "Optional, often admin",
+2
View File
@@ -182,7 +182,9 @@ export default withEnglishFallback({
password: "Contraseña", password: "Contraseña",
database: "Base de datos", database: "Base de datos",
databasePlaceholder: "Opcional", databasePlaceholder: "Opcional",
databasePlaceholderRequired: "Obligatorio, ingresa una base de datos existente",
databasePlaceholderWithDefault: "Opcional, por defecto {database}", databasePlaceholderWithDefault: "Opcional, por defecto {database}",
kingbaseDatabaseRequired: "Kingbase requiere una base de datos existente antes de conectar o cargar la lista de bases de datos.",
defaultDatabase: "BD predeterminada", defaultDatabase: "BD predeterminada",
authDatabase: "BD de autenticación", authDatabase: "BD de autenticación",
authDatabasePlaceholder: "Opcional, normalmente admin", authDatabasePlaceholder: "Opcional, normalmente admin",
+2
View File
@@ -181,7 +181,9 @@ export default withEnglishFallback({
password: "Password", password: "Password",
database: "Database", database: "Database",
databasePlaceholder: "Opzionale", databasePlaceholder: "Opzionale",
databasePlaceholderRequired: "Obbligatorio, inserisci un database esistente",
databasePlaceholderWithDefault: "Opzionale, predefinito {database}", databasePlaceholderWithDefault: "Opzionale, predefinito {database}",
kingbaseDatabaseRequired: "Kingbase richiede un database esistente prima di connettersi o caricare l'elenco dei database.",
defaultDatabase: "DB Predefinito", defaultDatabase: "DB Predefinito",
authDatabase: "DB di Autenticazione", authDatabase: "DB di Autenticazione",
authDatabasePlaceholder: "Opzionale, spesso admin", authDatabasePlaceholder: "Opzionale, spesso admin",
+2
View File
@@ -181,7 +181,9 @@ export default withEnglishFallback({
password: "パスワード", password: "パスワード",
database: "データベース", database: "データベース",
databasePlaceholder: "任意", databasePlaceholder: "任意",
databasePlaceholderRequired: "必須、既存のデータベースを入力",
databasePlaceholderWithDefault: "任意、デフォルトは {database}", databasePlaceholderWithDefault: "任意、デフォルトは {database}",
kingbaseDatabaseRequired: "Kingbase は接続またはデータベース一覧の読み込み前に、既存のデータベース名が必要です。",
defaultDatabase: "デフォルトDB", defaultDatabase: "デフォルトDB",
authDatabase: "認証DB", authDatabase: "認証DB",
authDatabasePlaceholder: "任意、通常は admin", authDatabasePlaceholder: "任意、通常は admin",
+2
View File
@@ -182,7 +182,9 @@ export default withEnglishFallback({
password: "Senha", password: "Senha",
database: "Banco de dados", database: "Banco de dados",
databasePlaceholder: "Opcional", databasePlaceholder: "Opcional",
databasePlaceholderRequired: "Obrigatório, informe um banco de dados existente",
databasePlaceholderWithDefault: "Opcional, padrão é {database}", databasePlaceholderWithDefault: "Opcional, padrão é {database}",
kingbaseDatabaseRequired: "Kingbase exige um banco de dados existente antes de conectar ou carregar a lista de bancos de dados.",
defaultDatabase: "BD padrão", defaultDatabase: "BD padrão",
authDatabase: "BD de autenticação", authDatabase: "BD de autenticação",
authDatabasePlaceholder: "Opcional, geralmente admin", authDatabasePlaceholder: "Opcional, geralmente admin",
+2
View File
@@ -182,7 +182,9 @@ export default withEnglishFallback({
password: "密码", password: "密码",
database: "数据库", database: "数据库",
databasePlaceholder: "可选", databasePlaceholder: "可选",
databasePlaceholderRequired: "必填,请填写已存在的数据库",
databasePlaceholderWithDefault: "可选,默认 {database}", databasePlaceholderWithDefault: "可选,默认 {database}",
kingbaseDatabaseRequired: "Kingbase 需要先填写一个已存在的数据库后才能连接或加载数据库列表。",
defaultDatabase: "默认库", defaultDatabase: "默认库",
authDatabase: "认证库", authDatabase: "认证库",
authDatabasePlaceholder: "可选,通常为 admin", authDatabasePlaceholder: "可选,通常为 admin",
+2
View File
@@ -182,7 +182,9 @@ export default withEnglishFallback({
password: "密碼", password: "密碼",
database: "資料庫", database: "資料庫",
databasePlaceholder: "可選", databasePlaceholder: "可選",
databasePlaceholderRequired: "必填,請填寫已存在的資料庫",
databasePlaceholderWithDefault: "可選,預設 {database}", databasePlaceholderWithDefault: "可選,預設 {database}",
kingbaseDatabaseRequired: "Kingbase 需要先填寫一個已存在的資料庫後才能連線或載入資料庫清單。",
defaultDatabase: "預設 DB", defaultDatabase: "預設 DB",
authDatabase: "驗證 DB", authDatabase: "驗證 DB",
authDatabasePlaceholder: "可選,通常為 admin", authDatabasePlaceholder: "可選,通常為 admin",
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { getDatabaseUserAdminProvider, kingbaseShowGrantsSql } from "@/lib/database/databaseUserAdmin";
describe("database user admin providers", () => {
it("uses sys_catalog for Kingbase role metadata", () => {
const provider = getDatabaseUserAdminProvider("kingbase");
expect(provider).not.toBeNull();
expect(provider?.dialect).toBe("postgres");
expect(provider?.listUsersSql()).toContain("FROM sys_catalog.sys_roles r");
expect(provider?.listUsersSql()).not.toContain("pg_catalog");
});
it("builds Kingbase grant SQL without PostgreSQL catalog tables", () => {
const sql = kingbaseShowGrantsSql({ user: "role'o", host: "LOGIN" });
expect(sql).toContain("FROM sys_catalog.sys_roles r");
expect(sql).toContain("FROM sys_catalog.sys_auth_members m");
expect(sql).toContain("CROSS JOIN sys_catalog.sys_database d");
expect(sql).toContain("CROSS JOIN sys_catalog.sys_namespace n");
expect(sql).toContain("WHERE r.rolname = 'role''o'");
expect(sql).not.toContain("pg_catalog");
expect(sql).not.toContain("pg_roles");
});
});
@@ -46,7 +46,8 @@ export interface DatabaseUserAdminProvider {
} }
export const MYSQL_USER_ADMIN_TYPES = new Set<DatabaseType>(["mysql", "goldendb"]); export const MYSQL_USER_ADMIN_TYPES = new Set<DatabaseType>(["mysql", "goldendb"]);
export const POSTGRES_USER_ADMIN_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "highgo", "kingbase", "kwdb", "opengauss", "questdb", "vastbase"]); export const KINGBASE_USER_ADMIN_TYPES = new Set<DatabaseType>(["kingbase"]);
export const POSTGRES_USER_ADMIN_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "highgo", "kwdb", "opengauss", "questdb", "vastbase"]);
export const MYSQL_COMMON_PRIVILEGES = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "INDEX", "REFERENCES", "EXECUTE", "SHOW VIEW", "TRIGGER", "EVENT", "CREATE TEMPORARY TABLES"] as const; export const MYSQL_COMMON_PRIVILEGES = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "INDEX", "REFERENCES", "EXECUTE", "SHOW VIEW", "TRIGGER", "EVENT", "CREATE TEMPORARY TABLES"] as const;
@@ -61,6 +62,7 @@ export function supportsDatabaseUserAdmin(dbType: DatabaseType | undefined): boo
export function getDatabaseUserAdminProvider(dbType: DatabaseType | undefined): DatabaseUserAdminProvider | null { export function getDatabaseUserAdminProvider(dbType: DatabaseType | undefined): DatabaseUserAdminProvider | null {
if (!dbType) return null; if (!dbType) return null;
if (MYSQL_USER_ADMIN_TYPES.has(dbType)) return mysqlUserAdminProvider; if (MYSQL_USER_ADMIN_TYPES.has(dbType)) return mysqlUserAdminProvider;
if (KINGBASE_USER_ADMIN_TYPES.has(dbType)) return kingbaseUserAdminProvider;
if (POSTGRES_USER_ADMIN_TYPES.has(dbType)) return postgresUserAdminProvider; if (POSTGRES_USER_ADMIN_TYPES.has(dbType)) return postgresUserAdminProvider;
return null; return null;
} }
@@ -257,6 +259,91 @@ FROM (
ORDER BY sort, line;`.trim(); ORDER BY sort, line;`.trim();
} }
export function kingbaseListRolesSql(): string {
return `
SELECT
r.rolname AS user,
CASE WHEN r.rolcanlogin THEN 'LOGIN' ELSE 'ROLE' END AS host,
concat_ws(', ',
CASE WHEN r.rolsuper THEN 'SUPERUSER' END,
CASE WHEN r.rolcreatedb THEN 'CREATEDB' END,
CASE WHEN r.rolcreaterole THEN 'CREATEROLE' END,
CASE WHEN r.rolreplication THEN 'REPLICATION' END,
CASE WHEN r.rolbypassrls THEN 'BYPASSRLS' END
) AS plugin
FROM sys_catalog.sys_roles r
ORDER BY r.rolname;`.trim();
}
export function kingbaseShowGrantsSql(user: DatabaseUserIdentity): string {
const role = quoteSqlString(user.user);
return `
WITH target AS (
SELECT oid, rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin, rolreplication, rolbypassrls
FROM sys_catalog.sys_roles r
WHERE r.rolname = ${role}
)
SELECT line
FROM (
SELECT 1 AS sort, 'Role: ' || quote_ident(rolname) AS line
FROM target
UNION ALL
SELECT 2, 'Attributes: ' || COALESCE(NULLIF(concat_ws(', ',
CASE WHEN rolsuper THEN 'SUPERUSER' END,
CASE WHEN rolcreatedb THEN 'CREATEDB' END,
CASE WHEN rolcreaterole THEN 'CREATEROLE' END,
CASE WHEN rolcanlogin THEN 'LOGIN' ELSE 'NOLOGIN' END,
CASE WHEN rolreplication THEN 'REPLICATION' END,
CASE WHEN rolbypassrls THEN 'BYPASSRLS' END
), ''), 'none')
FROM target
UNION ALL
SELECT 10, 'Member of: ' || quote_ident(parent.rolname) || CASE WHEN m.admin_option THEN ' WITH ADMIN OPTION' ELSE '' END
FROM sys_catalog.sys_auth_members m
JOIN target t ON t.oid = m.member
JOIN sys_catalog.sys_roles parent ON parent.oid = m.roleid
UNION ALL
SELECT 20, 'Has member: ' || quote_ident(member.rolname) || CASE WHEN m.admin_option THEN ' WITH ADMIN OPTION' ELSE '' END
FROM sys_catalog.sys_auth_members m
JOIN target t ON t.oid = m.roleid
JOIN sys_catalog.sys_roles member ON member.oid = m.member
UNION ALL
SELECT 30, 'Database: ' || quote_ident(d.datname) || ' = ' ||
concat_ws(', ',
CASE WHEN has_database_privilege(t.rolname, d.oid, 'CONNECT') THEN 'CONNECT' END,
CASE WHEN has_database_privilege(t.rolname, d.oid, 'CREATE') THEN 'CREATE' END,
CASE WHEN has_database_privilege(t.rolname, d.oid, 'TEMPORARY') THEN 'TEMPORARY' END
)
FROM target t
CROSS JOIN sys_catalog.sys_database d
WHERE has_database_privilege(t.rolname, d.oid, 'CONNECT')
OR has_database_privilege(t.rolname, d.oid, 'CREATE')
OR has_database_privilege(t.rolname, d.oid, 'TEMPORARY')
UNION ALL
SELECT 40, 'Schema: ' || quote_ident(n.nspname) || ' = ' ||
concat_ws(', ',
CASE WHEN has_schema_privilege(t.rolname, n.oid, 'USAGE') THEN 'USAGE' END,
CASE WHEN has_schema_privilege(t.rolname, n.oid, 'CREATE') THEN 'CREATE' END
)
FROM target t
CROSS JOIN sys_catalog.sys_namespace n
WHERE UPPER(n.nspname) <> 'INFORMATION_SCHEMA'
AND UPPER(n.nspname) NOT LIKE 'SYS%'
AND UPPER(n.nspname) NOT LIKE 'XLOG%'
AND (has_schema_privilege(t.rolname, n.oid, 'USAGE') OR has_schema_privilege(t.rolname, n.oid, 'CREATE'))
UNION ALL
SELECT 50, 'Table: ' || quote_ident(table_schema) || '.' || quote_ident(table_name) || ' = ' ||
string_agg(privilege_type || CASE WHEN is_grantable = 'YES' THEN ' WITH GRANT OPTION' ELSE '' END, ', ' ORDER BY privilege_type)
FROM information_schema.role_table_grants
WHERE grantee = ${role}
AND UPPER(table_schema) <> 'INFORMATION_SCHEMA'
AND UPPER(table_schema) NOT LIKE 'SYS%'
AND UPPER(table_schema) NOT LIKE 'XLOG%'
GROUP BY table_schema, table_name
) grants
ORDER BY sort, line;`.trim();
}
export function postgresCreateRoleSql(input: CreatePrincipalInput): string { export function postgresCreateRoleSql(input: CreatePrincipalInput): string {
const login = input.canLogin === false ? "NOLOGIN" : "LOGIN"; const login = input.canLogin === false ? "NOLOGIN" : "LOGIN";
return `CREATE ROLE ${quotePostgresIdentifier(input.user)} ${login} PASSWORD ${quoteSqlString(input.password)};`; return `CREATE ROLE ${quotePostgresIdentifier(input.user)} ${login} PASSWORD ${quoteSqlString(input.password)};`;
@@ -373,3 +460,9 @@ export const postgresUserAdminProvider: DatabaseUserAdminProvider = {
privilegesForScope: postgresPrivilegesForScope, privilegesForScope: postgresPrivilegesForScope,
defaultPrivilegesForScope: (scope) => (scope === "role" ? [] : [postgresDefaultPrivilege(scope)]), defaultPrivilegesForScope: (scope) => (scope === "role" ? [] : [postgresDefaultPrivilege(scope)]),
}; };
export const kingbaseUserAdminProvider: DatabaseUserAdminProvider = {
...postgresUserAdminProvider,
listUsersSql: kingbaseListRolesSql,
showGrantsSql: kingbaseShowGrantsSql,
};
+21 -1
View File
@@ -676,7 +676,7 @@ impl ConnectionConfig {
DatabaseType::Rqlite | DatabaseType::Turso => Some("main"), DatabaseType::Rqlite | DatabaseType::Turso => Some("main"),
DatabaseType::Gaussdb | DatabaseType::OpenGauss => Some("postgres"), DatabaseType::Gaussdb | DatabaseType::OpenGauss => Some("postgres"),
DatabaseType::Kwdb => Some("defaultdb"), DatabaseType::Kwdb => Some("defaultdb"),
DatabaseType::Kingbase | DatabaseType::Vastbase => Some("postgres"), DatabaseType::Vastbase => Some("postgres"),
DatabaseType::Highgo => Some("highgo"), DatabaseType::Highgo => Some("highgo"),
DatabaseType::Yashandb => Some("yasdb"), DatabaseType::Yashandb => Some("yasdb"),
DatabaseType::Oscar => Some("osrdb"), DatabaseType::Oscar => Some("osrdb"),
@@ -2153,6 +2153,26 @@ mod tests {
assert_eq!(config.effective_database(), Some("default")); assert_eq!(config.effective_database(), Some("default"));
} }
#[test]
fn kingbase_empty_database_has_no_default() {
let mut config = mysql_config("SYSTEM", "secret", None);
config.db_type = DatabaseType::Kingbase;
config.port = 54321;
assert_eq!(config.effective_database(), None);
assert_eq!(config.connection_url(), "kingbase://SYSTEM:secret@10.1.2.3:54321");
}
#[test]
fn vastbase_empty_database_uses_postgres_for_connection() {
let mut config = mysql_config("vastbase", "secret", None);
config.db_type = DatabaseType::Vastbase;
config.port = 5432;
assert_eq!(config.effective_database(), Some("postgres"));
assert_eq!(config.connection_url(), "vastbase://vastbase:secret@10.1.2.3:5432/postgres");
}
#[test] #[test]
fn clickhouse_tls_uses_https_from_ssl_or_secure_param() { fn clickhouse_tls_uses_https_from_ssl_or_secure_param() {
let mut config = mysql_config("default", "", None); let mut config = mysql_config("default", "", None);