Skip to content

Commit

Permalink
Merge pull request #27 from joshuashaffer/schemaspy-metadata
Browse files Browse the repository at this point in the history
Schemaspy metadata
  • Loading branch information
Mause authored Jul 2, 2024
2 parents be08302 + 55d5703 commit 0e5cd92
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 20 deletions.
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>

0 comments on commit 0e5cd92

Please sign in to comment.