From d64299b59e46ee20e1ee21f576a2bcfdbef8aa76 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 3 Jul 2026 16:34:47 +0800 Subject: [PATCH] fix(gbase): load routines in object list --- .../com/dbx/agent/gbase8a/Gbase8aAgent.java | 46 +++++++ .../dbx/agent/gbase8a/Gbase8aAgentTest.java | 129 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 agents/drivers/gbase8a/src/test/java/com/dbx/agent/gbase8a/Gbase8aAgentTest.java diff --git a/agents/drivers/gbase8a/src/main/java/com/dbx/agent/gbase8a/Gbase8aAgent.java b/agents/drivers/gbase8a/src/main/java/com/dbx/agent/gbase8a/Gbase8aAgent.java index f3a38d849b..374a26eed2 100644 --- a/agents/drivers/gbase8a/src/main/java/com/dbx/agent/gbase8a/Gbase8aAgent.java +++ b/agents/drivers/gbase8a/src/main/java/com/dbx/agent/gbase8a/Gbase8aAgent.java @@ -4,9 +4,11 @@ import com.dbx.agent.ConfiguredJdbcAgent; import com.dbx.agent.ExecuteQueryOptions; import com.dbx.agent.JdbcAgentProfile; import com.dbx.agent.JsonRpcServer; +import com.dbx.agent.ObjectInfo; import com.dbx.agent.QueryPageOptions; import com.dbx.agent.QueryPageResult; import com.dbx.agent.QueryResult; +import com.dbx.agent.StandardJdbcMetadata; import com.dbx.agent.TableInfo; import java.sql.PreparedStatement; @@ -14,6 +16,7 @@ import java.sql.ResultSet; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Locale; public final class Gbase8aAgent extends ConfiguredJdbcAgent { public static final JdbcAgentProfile GBASE8A_PROFILE = new JdbcAgentProfile( @@ -79,6 +82,49 @@ public final class Gbase8aAgent extends ConfiguredJdbcAgent { }); } + @Override + public List listObjects(String schema) { + return unchecked(() -> { + List result = StandardJdbcMetadata.INSTANCE.listObjects(listTables(schema), schema); + // GBase 8a's table metadata does not surface routines, so sidebar objects must load them explicitly. + appendRoutines(result, schema); + return result; + }); + } + + private void appendRoutines(List result, String schema) throws Exception { + String sql; + boolean hasSchema = schema != null && !schema.trim().isEmpty(); + if (hasSchema) { + sql = "SELECT ROUTINE_NAME, ROUTINE_TYPE, ROUTINE_COMMENT FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ? ORDER BY ROUTINE_NAME"; + } else { + sql = "SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, ROUTINE_COMMENT FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'gclusterdb', 'gctmpdb') ORDER BY ROUTINE_SCHEMA, ROUTINE_NAME"; + } + try (PreparedStatement stmt = requireConnection().prepareStatement(sql)) { + if (hasSchema) { + stmt.setString(1, schema); + } + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + String routineSchema = hasSchema ? schema : rs.getString("ROUTINE_SCHEMA"); + result.add(new ObjectInfo( + rs.getString("ROUTINE_NAME"), + normalizeRoutineType(rs.getString("ROUTINE_TYPE")), + routineSchema, + rs.getString("ROUTINE_COMMENT") + )); + } + } + } + } + + private static String normalizeRoutineType(String routineType) { + if (routineType == null || routineType.trim().isEmpty()) { + return "PROCEDURE"; + } + return routineType.trim().toUpperCase(Locale.ROOT); + } + private static ExecuteQueryOptions withoutFetchSize(ExecuteQueryOptions options) { return new ExecuteQueryOptions(options.getMaxRows(), null, options.getTimeoutSecs()); } diff --git a/agents/drivers/gbase8a/src/test/java/com/dbx/agent/gbase8a/Gbase8aAgentTest.java b/agents/drivers/gbase8a/src/test/java/com/dbx/agent/gbase8a/Gbase8aAgentTest.java new file mode 100644 index 0000000000..f6f281e62a --- /dev/null +++ b/agents/drivers/gbase8a/src/test/java/com/dbx/agent/gbase8a/Gbase8aAgentTest.java @@ -0,0 +1,129 @@ +package com.dbx.agent.gbase8a; + +import com.dbx.agent.ObjectInfo; +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.util.ArrayList; +import java.util.List; + +class Gbase8aAgentTest { + @Test + void listObjectsIncludesRoutinesFromInformationSchema() { + List sql = new ArrayList<>(); + Gbase8aAgent agent = new Gbase8aAgent(); + TestSupport.setPrivateConnection(agent, preparedConnection(sql, + resultSet( + new String[]{"TABLE_NAME", "TABLE_TYPE"}, + new Object[][]{{"orders", "BASE TABLE"}} + ), + resultSet( + new String[]{"ROUTINE_NAME", "ROUTINE_TYPE", "ROUTINE_COMMENT"}, + new Object[][]{{"refresh_orders", "PROCEDURE", "proc comment"}, {"format_order", "FUNCTION", null}} + ) + )); + + List objects = agent.listObjects("app"); + + Assertions.assertEquals(3, objects.size()); + Assertions.assertEquals("orders", objects.get(0).getName()); + Assertions.assertEquals("TABLE", objects.get(0).getObject_type()); + Assertions.assertEquals("refresh_orders", objects.get(1).getName()); + Assertions.assertEquals("PROCEDURE", objects.get(1).getObject_type()); + Assertions.assertEquals("app", objects.get(1).getSchema()); + Assertions.assertEquals("proc comment", objects.get(1).getComment()); + Assertions.assertEquals("format_order", objects.get(2).getName()); + Assertions.assertEquals("FUNCTION", objects.get(2).getObject_type()); + Assertions.assertEquals("app", objects.get(2).getSchema()); + Assertions.assertTrue(sql.get(1).contains("FROM information_schema.ROUTINES"), sql.get(1)); + Assertions.assertTrue(sql.get(1).contains("ROUTINE_SCHEMA = ?"), sql.get(1)); + } + + 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()) || "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 ("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 value = columnValue(columns, rows[index[0]], args[0]); + return value == null ? null : String.valueOf(value); + 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 T proxy(Class type, MethodHandler handler) { + InvocationHandler invocationHandler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return handler.handle(method, args == null ? new Object[0] : args); + } + }; + return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, invocationHandler)); + } + + private static Object defaultValue(Class type) { + if (type == Boolean.TYPE) return false; + if (type == Byte.TYPE) return (byte) 0; + if (type == Short.TYPE) return (short) 0; + if (type == Integer.TYPE) return 0; + if (type == Long.TYPE) return 0L; + if (type == Float.TYPE) return 0f; + if (type == Double.TYPE) return 0d; + if (type == Character.TYPE) return (char) 0; + return null; + } + + private interface MethodHandler { + Object handle(Method method, Object[] args) throws Throwable; + } +}