diff --git a/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java b/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java index a92d1ce1b..7d75e418a 100644 --- a/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java +++ b/agents/common/src/main/java/com/dbx/agent/ConfiguredJdbcAgent.java @@ -73,15 +73,24 @@ public abstract class ConfiguredJdbcAgent extends AbstractJdbcAgent { @Override public List listObjects(String schema) { - return StandardJdbcMetadata.INSTANCE.listObjects(listTables(schema), schema); + return StandardJdbcMetadata.INSTANCE.listObjects( + requireConnection(), + profile, + configuredDatabase, + schema, + MetadataListConstraints.NONE + ); } @Override public List listObjects(String schema, MetadataListConstraints constraints) { - MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints); - MetadataListConstraints tableConstraints = - new MetadataListConstraints(normalized.getFilter(), null, null, normalized.getObjectTypes()); - return StandardJdbcMetadata.INSTANCE.listObjects(listTables(schema, tableConstraints), schema, normalized); + return StandardJdbcMetadata.INSTANCE.listObjects( + requireConnection(), + profile, + configuredDatabase, + schema, + constraints + ); } @Override diff --git a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java index fe395be68..9a6eada6f 100644 --- a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java +++ b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java @@ -108,6 +108,33 @@ public final class StandardJdbcMetadata { return MetadataListConstraints.orNone(constraints).filterObjects(result); } + public List listObjects( + Connection conn, + JdbcAgentProfile profile, + String configuredDatabase, + String schema, + MetadataListConstraints constraints + ) { + return unchecked(() -> { + MetadataListConstraints normalized = MetadataListConstraints.orNone(constraints); + MetadataListConstraints tableConstraints = + new MetadataListConstraints(normalized.getFilter(), null, null, normalized.getObjectTypes()); + List result = new ArrayList<>( + listObjects(listTables(conn, profile, configuredDatabase, schema, tableConstraints), schema) + ); + DatabaseMetaData meta = conn.getMetaData(); + appendRoutines(result, meta, null, blankToNull(schema), schema); + if (!containsRoutine(result) + && profile.getCatalogFallbackEnabled() + && configuredDatabase != null + && !configuredDatabase.trim().isEmpty()) { + appendRoutines(result, meta, configuredDatabase, blankToNull(schema), schema); + } + result.sort(Comparator.comparing(ObjectInfo::getName)); + return normalized.filterObjects(result); + }); + } + public List listDataTypes(Connection conn) { return unchecked(() -> { Set seen = new LinkedHashSet<>(); @@ -587,6 +614,47 @@ public final class StandardJdbcMetadata { return type; } + private static void appendRoutines( + List result, + DatabaseMetaData meta, + String catalog, + String schemaPattern, + String schema + ) { + Set procedureNames = new LinkedHashSet<>(); + try (ResultSet rs = meta.getProcedures(catalog, schemaPattern, "%")) { + while (rs.next()) { + String name = rs.getString("PROCEDURE_NAME"); + if (name != null && !name.trim().isEmpty()) { + procedureNames.add(name); + result.add(new ObjectInfo(name, "PROCEDURE", schema, rs.getString("REMARKS"))); + } + } + } catch (Exception | AbstractMethodError ignored) { + // Some JDBC drivers don't implement routine metadata. + } + try (ResultSet rs = meta.getFunctions(catalog, schemaPattern, "%")) { + while (rs.next()) { + String name = rs.getString("FUNCTION_NAME"); + if (name != null && !name.trim().isEmpty() && !procedureNames.contains(name)) { + result.add(new ObjectInfo(name, "FUNCTION", schema, rs.getString("REMARKS"))); + } + } + } catch (Exception | AbstractMethodError ignored) { + // Some JDBC drivers don't implement function metadata separately. + } + } + + private static boolean containsRoutine(List objects) { + for (ObjectInfo object : objects) { + if ("PROCEDURE".equalsIgnoreCase(object.getObject_type()) + || "FUNCTION".equalsIgnoreCase(object.getObject_type())) { + return true; + } + } + return false; + } + private static Integer intOrNull(ResultSet rs, String column) throws Exception { Object value = rs.getObject(column); return value instanceof Number ? ((Number) value).intValue() : null; diff --git a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java index 34231659c..1ae5e8d13 100644 --- a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java +++ b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java @@ -108,6 +108,30 @@ class StandardJdbcMetadataTest { assertEquals("TABLE", tables.get(1).getTable_type()); } + @Test + void listsProceduresAndFunctionsFromJdbcRoutineMetadata() { + Connection conn = routineConnection( + rows( + row("PROCEDURE_NAME", "PROCESS_ORDER", "REMARKS", "processes an order"), + row("PROCEDURE_NAME", "SHARED_ROUTINE", "REMARKS", null) + ), + rows( + row("FUNCTION_NAME", "CALCULATE_TOTAL", "REMARKS", "calculates a total"), + row("FUNCTION_NAME", "SHARED_ROUTINE", "REMARKS", null) + ) + ); + MetadataListConstraints constraints = + new MetadataListConstraints(null, null, null, Collections.singletonList("PROCEDURE")); + + List objects = StandardJdbcMetadata.INSTANCE.listObjects(conn, profile, "", "APP", constraints); + + assertEquals(2, objects.size()); + assertEquals("PROCESS_ORDER", objects.get(0).getName()); + assertEquals("PROCEDURE", objects.get(0).getObject_type()); + assertEquals("SHARED_ROUTINE", objects.get(1).getName()); + assertEquals("PROCEDURE", objects.get(1).getObject_type()); + } + @Test void listsDataTypesFromJdbcTypeInfo() { Connection conn = connection( @@ -581,6 +605,34 @@ class StandardJdbcMetadataTest { }); } + private static Connection routineConnection(ResultSet procedures, ResultSet functions) { + DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + String name = method.getName(); + if ("getTables".equals(name) || "getTableTypes".equals(name)) { + return rows(); + } + if ("getProcedures".equals(name)) { + return procedures; + } + if ("getFunctions".equals(name)) { + return functions; + } + return defaultValue(method.getReturnType()); + } + }); + return proxy(Connection.class, new MethodHandler() { + @Override + public Object handle(Method method, Object[] args) { + if ("getMetaData".equals(method.getName())) { + return meta; + } + return defaultValue(method.getReturnType()); + } + }); + } + private static ResultSet orderColumns() { return rows(row( "COLUMN_NAME", "ID",