Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schemaspy metadata #27

Merged
merged 8 commits into from
Jul 2, 2024
128 changes: 108 additions & 20 deletions src/main/java/org/duckdb/DuckDBDatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import javax.sql.rowset.RowSetProvider;

public class DuckDBDatabaseMetaData implements DatabaseMetaData {

DuckDBConnection conn;

public DuckDBDatabaseMetaData(DuckDBConnection conn) {
Expand Down Expand Up @@ -170,27 +171,74 @@ public String getIdentifierQuoteString() throws SQLException {

@Override
public String getSQLKeywords() throws SQLException {
throw new SQLFeatureNotSupportedException("getSQLKeywords");
Statement statement = conn.createStatement();
statement.closeOnCompletion();
ResultSet rs = statement.executeQuery("SELECT keyword_name FROM duckdb_keywords()");
StringBuilder sb = new StringBuilder();
while (rs.next()) {
sb.append(rs.getString(1));
sb.append(',');
}
return sb.toString();
}

@Override
public String getNumericFunctions() throws SQLException {
throw new SQLFeatureNotSupportedException("getNumericFunctions");
Statement statement = conn.createStatement();
statement.closeOnCompletion();
ResultSet rs = statement.executeQuery("SELECT DISTINCT function_name FROM duckdb_functions() "
+ "WHERE parameter_types[1] ='DECIMAL'"
+ "OR parameter_types[1] ='DOUBLE'"
+ "OR parameter_types[1] ='SMALLINT'"
+ "OR parameter_types[1] = 'BIGINT'");
StringBuilder sb = new StringBuilder();
while (rs.next()) {
sb.append(rs.getString(1));
sb.append(',');
}
return sb.toString();
}

@Override
public String getStringFunctions() throws SQLException {
throw new SQLFeatureNotSupportedException("getStringFunctions");
Statement statement = conn.createStatement();
statement.closeOnCompletion();
ResultSet rs = statement.executeQuery(
"SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'");
StringBuilder sb = new StringBuilder();
while (rs.next()) {
sb.append(rs.getString(1));
sb.append(',');
}
return sb.toString();
}

@Override
public String getSystemFunctions() throws SQLException {
throw new SQLFeatureNotSupportedException("getSystemFunctions");
Statement statement = conn.createStatement();
statement.closeOnCompletion();
ResultSet rs = statement.executeQuery(
"SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0");
StringBuilder sb = new StringBuilder();
while (rs.next()) {
sb.append(rs.getString(1));
sb.append(',');
}
return sb.toString();
}

@Override
public String getTimeDateFunctions() throws SQLException {
throw new SQLFeatureNotSupportedException("getTimeDateFunctions");
Statement statement = conn.createStatement();
statement.closeOnCompletion();
ResultSet rs = statement.executeQuery(
"SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'");
StringBuilder sb = new StringBuilder();
while (rs.next()) {
sb.append(rs.getString(1));
sb.append(',');
}
return sb.toString();
}

@Override
Expand Down Expand Up @@ -730,14 +778,16 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
str.append(", NULL::VARCHAR AS 'REF_GENERATION'").append(lineSeparator());
str.append("FROM information_schema.tables").append(lineSeparator());

// tableNamePattern - a table name pattern; must match the table name as it is stored in the database
// tableNamePattern - a table name pattern; must match the table name as it
// is stored in the database
if (tableNamePattern == null) {
// non-standard behavior.
tableNamePattern = "%";
}
str.append("WHERE table_name LIKE ?").append(lineSeparator());

// catalog - a catalog name; must match the catalog name as it is stored in the database;
// catalog - a catalog name; must match the catalog name as it is stored in
// the database;
// "" retrieves those without a catalog;
// null means that the catalog name should not be used to narrow the search
boolean hasCatalogParam = false;
Expand All @@ -751,7 +801,8 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
}
}

// schemaPattern - a schema name pattern; must match the schema name as it is stored in the database;
// schemaPattern - a schema name pattern; must match the schema name as it
// is stored in the database;
// "" retrieves those without a schema;
// null means that the schema name should not be used to narrow the search
boolean hasSchemaParam = false;
Expand Down Expand Up @@ -979,7 +1030,43 @@ public ResultSet getTypeInfo() throws SQLException {
@Override
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
throws SQLException {
throw new SQLFeatureNotSupportedException("getIndexInfo(");
StringBuilder sb = new StringBuilder();
sb.append("SELECT database_name AS TABLE_CAT "
+ ", schema_name AS TABLE_SCHEM "
+ ", table_name AS TABLE_NAME "
+ ", index_name AS INDEX_NAME "
+ ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE "
+ ", NULL AS TYPE "
+ ", NULL AS ORDINAL_POSITION "
+ ", NULL AS COLUMN_NAME "
+ ", NULL AS ASC_OR_DESC "
+ ", NULL AS CARDINALITY "
+ ", NULL AS PAGES "
+ ", NULL AS FILTER_CONDITION "
+ "FROM duckdb_indexes() WHERE TRUE ");
if (catalog != null) {
sb.append(" AND database_name = ?");
}
if (schema != null) {
sb.append(" AND schema_name = ?");
}
if (table != null) {
sb.append(" AND table_name = ?");
}
sb.append(" ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION");
PreparedStatement ps = conn.prepareStatement(sb.toString());
int paramIndex = 1;
if (catalog != null) {
ps.setString(paramIndex++, catalog);
}
if (schema != null) {
ps.setString(paramIndex++, schema);
}
if (table != null) {
ps.setString(paramIndex++, table);
}
ps.closeOnCompletion();
return ps.executeQuery();
}

@Override
Expand Down Expand Up @@ -1158,25 +1245,26 @@ public ResultSet getClientInfoProperties() throws SQLException {
*
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* <code>null</code> means that the catalog name should not be used to narrow
* the search
* <code>null</code> means that the catalog name should not be used to
* narrow the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* <code>null</code> means that the schema name should not be used to narrow
* the search
* as it is stored in the database; "" retrieves those without a
* schema; <code>null</code> means that the schema name should not be used to
* narrow the search
* @param functionNamePattern a function name pattern; must match the
* function name as it is stored in the database
* FUNCTION_CAT String => function catalog (may be null)
* FUNCTION_SCHEM String => function schema (may be null)
* FUNCTION_NAME String => function name. This is the name used to invoke the function
* REMARKS String => explanatory comment on the function
* FUNCTION_NAME String => function name. This is the name used to invoke the
* function REMARKS String => explanatory comment on the function
* FUNCTION_TYPE short => kind of function:
* - functionResultUnknown - Cannot determine if a return value or table will be returned
* - functionResultUnknown - Cannot determine if a return value or table will
* be returned
* - functionNoTable- Does not return a table
* - functionReturnsTable - Returns a table
* SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. This is a user
* specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload
* functions
* SPECIFIC_NAME String => the name which uniquely identifies this function
* within its schema. This is a user specified, or DBMS generated, name that
* may be different then the FUNCTION_NAME for example with overload functions
*/
@Override
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
Expand Down
74 changes: 74 additions & 0 deletions src/test/java/org/duckdb/TestDuckDBJDBC.java
Original file line number Diff line number Diff line change
Expand Up @@ -4420,6 +4420,80 @@ public static void test_column_metadata() throws Exception {
}
}

public static void test_metadata_get_sql_keywords() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
String rs = conn.getMetaData().getSQLKeywords();
String[] keywords = rs.split(",");
List<String> list = asList(keywords);
assertTrue(list.contains("select"));
assertTrue(list.contains("update"));
assertTrue(list.contains("delete"));
assertTrue(list.contains("drop"));
}
}

public static void test_metadata_get_numeric_functions() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
String rs = conn.getMetaData().getNumericFunctions();
// print out rs
String[] functions = rs.split(",");
List<String> list = asList(functions);
assertTrue(list.contains("abs"));
assertTrue(list.contains("ceil"));
assertTrue(list.contains("floor"));
assertTrue(list.contains("round"));
}
}

public static void test_metadata_get_string_functions() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
String rs = conn.getMetaData().getStringFunctions();
String[] functions = rs.split(",");
List<String> list = asList(functions);
assertTrue(list.contains("md5"));
assertTrue(list.contains("json_keys"));
assertTrue(list.contains("repeat"));
assertTrue(list.contains("from_base64"));
}
}

public static void test_metadata_get_system_functions() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
String rs = conn.getMetaData().getSystemFunctions();
String[] functions = rs.split(",");
List<String> list = asList(functions);
assertTrue(list.contains("current_date"));
assertTrue(list.contains("now"));
}
}

public static void test_metadata_get_time_date_functions() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
String rs = conn.getMetaData().getTimeDateFunctions();
String[] functions = rs.split(",");
List<String> list = asList(functions);
assertTrue(list.contains("day"));
assertTrue(list.contains("dayname"));
assertTrue(list.contains("timezone_hour"));
}
}

public static void test_metadata_get_index_info() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
try (Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE test (id INT PRIMARY KEY, ok INT)");
stmt.execute("CREATE INDEX idx_test_ok ON test(ok)");
}

try (ResultSet rs = conn.getMetaData().getIndexInfo(null, null, "test", false, false)) {
assertTrue(rs.next());
assertEquals(rs.getString("TABLE_NAME"), "test");
assertEquals(rs.getString("INDEX_NAME"), "idx_test_ok");
assertEquals(rs.getBoolean("NON_UNIQUE"), true);
}
}
}

public static void main(String[] args) throws Exception {
System.exit(runTests(args, TestDuckDBJDBC.class, TestExtensionTypes.class));
}
Expand Down
11 changes: 11 additions & 0 deletions src/test/test.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Loading