fix(gbase): load routines in object list

This commit is contained in:
t8y2 2026-07-03 16:34:47 +08:00
parent 0894334764
commit d64299b59e
2 changed files with 175 additions and 0 deletions

View File

@ -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<ObjectInfo> listObjects(String schema) {
return unchecked(() -> {
List<ObjectInfo> 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<ObjectInfo> 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());
}

View File

@ -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<String> 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<ObjectInfo> 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<String> 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> T proxy(Class<T> 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;
}
}