fix(kingbase): list views and routines

This commit is contained in:
t8y2 2026-06-26 15:48:18 +08:00
parent f1c4604aeb
commit 4e5cb62102
2 changed files with 176 additions and 9 deletions

View File

@ -90,21 +90,84 @@ public final class KingbaseAgent extends PostgresLikeAgent {
@Override
public List<TableInfo> listTables(String schema) {
return listTables(schema, "table_type = 'BASE TABLE'");
if (isMysqlCompatMode()) {
return listTables(schema, "table_type IN ('BASE TABLE', 'VIEW')");
}
return unchecked(() -> {
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
public List<ObjectInfo> listObjects(String schema) {
List<ObjectInfo> result = new ArrayList<>();
for (TableInfo table : listTables(schema)) {
result.add(new ObjectInfo(table.getName(), table.getTable_type(), effectiveSchema(schema), table.getComment()));
}
return result;
return unchecked(() -> {
String effectiveSchema = effectiveSchema(schema);
List<ObjectInfo> 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 p.proname AS routine_name, " +
"CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type, " +
"d.description AS routine_comment " +
"FROM sys_catalog.sys_proc p " +
"JOIN sys_catalog.sys_namespace n ON n.oid = p.pronamespace " +
"LEFT JOIN sys_catalog.sys_description d ON d.objoid = p.oid AND d.objsubid = 0 " +
"WHERE n.nspname = ? AND p.prokind IN ('p','f') " +
"ORDER BY p.proname";
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
stmt.setString(1, effectiveSchema);
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 ObjectSource getObjectSource(String schema, String name, String objectType) {
if (!"VIEW".equalsIgnoreCase(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(() -> {
@ -124,6 +187,29 @@ public final class KingbaseAgent extends PostgresLikeAgent {
});
}
private ObjectSource routineSource(String schema, String name, String objectType) {
return unchecked(() -> {
String source = "";
String prokind = "PROCEDURE".equalsIgnoreCase(objectType) ? "p" : "f";
String sql = "SELECT sys_get_functiondef(p.oid) AS source " +
"FROM sys_catalog.sys_proc p " +
"JOIN sys_catalog.sys_namespace n ON n.oid = p.pronamespace " +
"WHERE n.nspname = ? AND p.proname = ? AND p.prokind = ? " +
"ORDER BY p.oid LIMIT 1";
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
stmt.setString(1, effectiveSchema(schema));
stmt.setString(2, name);
stmt.setString(3, prokind);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
source = coalesce(rs.getString("source"));
}
}
}
return new ObjectSource(name, objectType, effectiveSchema(schema), source);
});
}
@Override
public List<ColumnInfo> getColumns(String schema, String table) {
return unchecked(() -> {

View File

@ -2,6 +2,9 @@ package com.dbx.agent.kingbase;
import com.dbx.agent.DatabaseAgent;
import com.dbx.agent.DatabaseInfo;
import com.dbx.agent.ObjectInfo;
import com.dbx.agent.ObjectSource;
import com.dbx.agent.TableInfo;
import com.dbx.agent.test.JdbcFakeExecutionBehaviorTest;
import com.dbx.agent.test.TestSupport;
import org.junit.jupiter.api.Assertions;
@ -98,6 +101,72 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Assertions.assertFalse(sql.get(0).contains("SHOW"));
}
@Test
void regularListTablesUsesKingbaseCatalogAndIncludesViews() {
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[][]{{"app_table", "TABLE", "table comment"}, {"app_view", "VIEW", "view comment"}}
)));
List<TableInfo> 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"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("c.relkind IN ('r','p','v','m','f')"), sql.get(0));
}
@Test
void regularListObjectsIncludesKingbaseViewsProceduresAndFunctions() {
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[][]{{"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<ObjectInfo> 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.prokind IN ('p','f')"), sql.get(1));
}
@Test
void regularRoutineSourceUsesKingbaseFunctionDefinition() {
List<String> 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));
}
@Test
void mysqlCompatTimestampTypeNameIsReadAsTimestampText() throws Exception {
Timestamp timestamp = Timestamp.valueOf("2026-06-22 11:29:00");
@ -110,9 +179,19 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
}
private static Connection preparedConnection(List<String> sql, ResultSet rs) {
return preparedConnection(sql, new ResultSet[]{rs});
}
private static Connection preparedConnection(List<String> sql, ResultSet... resultSets) {
int[] resultSetIndex = {0};
PreparedStatement statement = proxy(PreparedStatement.class, (method, args) -> {
if ("executeQuery".equals(method.getName())) {
return rs;
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;
@ -122,7 +201,9 @@ class KingbaseAgentTest extends JdbcFakeExecutionBehaviorTest {
Statement plainStatement = proxy(Statement.class, (method, args) -> {
if ("executeQuery".equals(method.getName())) {
sql.add(String.valueOf(args[0]));
return rs;
int current = Math.min(resultSetIndex[0], resultSets.length - 1);
resultSetIndex[0] += 1;
return resultSets[current];
}
if ("close".equals(method.getName())) {
return null;