postgresql-jdbc.git / summary / log / commit / refs

commit    17cac52fe9cb80001cfd1fd72ec2b03fa4c5d64e
Author:   Sophiah Ho <182134868+SophiahHo@users.noreply.github.com>
Commit:   GitHub <noreply@github.com>
Date:     Thu Dec 12 17:20:54 2024 +0000

    Fix PgDatabaseMetaData implementation of catalog as param and return value (#3390)
    
    * fix: fix getSchemas(String catalog, String schemaPattern) to process catalog parameter and return catalog metadata per JDBC spec
    
    
    * fix: populate TABLE_CATALOG with current_database()::information_schema.sql_identifier in getSchemas
    
    * fix: include catalog filter in getSchemas
    
    * fix: an empty string for schemaPattern should return no rows instead of being ignored per JDBC spec
    
    * test: update getSchemas() test with null catalog argument and assert TABLE_CATALOG in ResultSet is not null
    
    * test: add new tests for getSchemas
    
    * fix: update getTables to follow JDBC spec
    
    * fix: use top-level Javadoc spec on interface and align with JDBC implementation convention of other databases
    
    * fix: filter by catalog in getColumns
    
    * fix: update getFunctions, getFunctionColumns to follow JDBC spec
    
    * fix: update getProcedures, getProcedureColumns to follow JDBC spec
    
    * fix: update getIndexInfo per JDBC spec
    
    * fix: update getPrimaryKeys per JDBC spec
    
    * fix: update other methods in PgDatabaseMetaData to match specifications in DatabaseMetaData interface
    
    * test: use null for catalog arg
    
    * test: update tests
    
    * test: add tests for empty catalog and schema arguments
    
    * test: add test for percent sign catalog argument
    
    * test: add tests for empty and percent sign catalog, empty schema
    
    * test: add tests for percent sign catalog argument
    
    * test: use empty functionNamePattern in getFunctionsWithBlankPatterns
    
    * test: update tests to use try-with-resources for Connection and Statement
    
    * fix: fix error messages
    
    ---------
    
    Co-authored-by: Sophiah Ho <sophiah.ho@precisely.com>


.../org/postgresql/jdbc/PgDatabaseMetaData.java | 180 ++++-- .../test/jdbc2/DatabaseMetaDataTest.java | 659 ++++++++++++++------- .../test/jdbc3/DatabaseMetaDataTest.java | 10 +- .../test/jdbc4/DatabaseMetaDataTest.java | 311 ++++++++-- .../test/jdbc42/DatabaseMetaDataTest.java | 31 +- 5 files changed, 875 insertions(+), 316 deletions(-) diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java index fc342454..0c749689 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java @@ -1188,7 +1188,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { @Nullable String procedureNamePattern) throws SQLException { String sql; - sql = "SELECT NULL AS \"PROCEDURE_CAT\", n.nspname AS \"PROCEDURE_SCHEM\", p.proname AS \"PROCEDURE_NAME\", " + sql = "SELECT current_database() AS \"PROCEDURE_CAT\", " + + " n.nspname AS \"PROCEDURE_SCHEM\", p.proname AS \"PROCEDURE_NAME\", " + "NULL, NULL, NULL, d.description AS \"REMARKS\", " + DatabaseMetaData.procedureReturnsResult + " AS \"PROCEDURE_TYPE\", " + " p.proname || '_' || p.oid AS \"SPECIFIC_NAME\" " @@ -1201,10 +1202,13 @@ public class PgDatabaseMetaData implements DatabaseMetaData { if (connection.haveMinimumServerVersion(ServerVersion.v11)) { sql += " AND p.prokind='p'"; } - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (procedureNamePattern != null && !procedureNamePattern.isEmpty()) { + if (procedureNamePattern != null) { sql += " AND p.proname LIKE " + escapeQuotes(procedureNamePattern); } if (connection.getHideUnprivilegedObjects()) { @@ -1246,14 +1250,17 @@ public class PgDatabaseMetaData implements DatabaseMetaData { f[19] = new Field("SPECIFIC_NAME", Oid.VARCHAR); String sql; - sql = "SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid, " + sql = "SELECT current_database(), n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid, " + " p.proargnames, p.proargmodes, p.proallargtypes, p.oid " + " FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_type t " + " WHERE p.pronamespace=n.oid AND p.prorettype=t.oid "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (procedureNamePattern != null && !procedureNamePattern.isEmpty()) { + if (procedureNamePattern != null) { sql += " AND p.proname LIKE " + escapeQuotes(procedureNamePattern); } sql += " ORDER BY n.nspname, p.proname, p.oid::text "; @@ -1263,6 +1270,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { + byte[] catalogName = rs.getBytes("current_database"); byte[] schema = rs.getBytes("nspname"); byte[] procedureName = rs.getBytes("proname"); byte[] specificName = @@ -1303,7 +1311,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { if ("b".equals(returnTypeType) || "d".equals(returnTypeType) || "e".equals(returnTypeType) || ("p".equals(returnTypeType) && argModesArray == null)) { byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = procedureName; tuple[3] = connection.encodeString("returnValue"); @@ -1329,7 +1337,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { // Add a row for each argument. for (int i = 0; i < numArgs; i++) { byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = procedureName; @@ -1385,7 +1393,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { while (columnrs.next()) { int columnTypeOid = (int) columnrs.getLong("atttypid"); byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = procedureName; tuple[3] = columnrs.getBytes("attname"); @@ -1423,7 +1431,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { String select; String orderby; String useSchemas = "SCHEMAS"; - select = "SELECT NULL AS \"TABLE_CAT\", n.nspname AS \"TABLE_SCHEM\", c.relname AS \"TABLE_NAME\", " + select = "SELECT current_database() AS \"TABLE_CAT\", n.nspname AS \"TABLE_SCHEM\", c.relname AS \"TABLE_NAME\", " + " CASE n.nspname ~ '^pg_' OR n.nspname = 'information_schema' " + " WHEN true THEN CASE " + " WHEN n.nspname = 'pg_catalog' OR n.nspname = 'information_schema' THEN CASE c.relkind " @@ -1467,7 +1475,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0 and d.classoid = 'pg_class'::regclass) " + " WHERE c.relnamespace = n.oid "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + select += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schemaPattern != null) { select += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } @@ -1488,9 +1500,10 @@ public class PgDatabaseMetaData implements DatabaseMetaData { orderby = " ORDER BY \"TABLE_TYPE\",\"TABLE_SCHEM\",\"TABLE_NAME\" "; - if (tableNamePattern != null && !tableNamePattern.isEmpty()) { + if (tableNamePattern != null) { select += " AND c.relname LIKE " + escapeQuotes(tableNamePattern); } + if (types != null) { select += " AND (false "; StringBuilder orclause = new StringBuilder(); @@ -1603,11 +1616,14 @@ public class PgDatabaseMetaData implements DatabaseMetaData { public ResultSet getSchemas(@Nullable String catalog, @Nullable String schemaPattern) throws SQLException { String sql; - sql = "SELECT nspname AS \"TABLE_SCHEM\", NULL AS \"TABLE_CATALOG\" FROM pg_catalog.pg_namespace " + sql = "SELECT nspname AS \"TABLE_SCHEM\", current_database() AS \"TABLE_CATALOG\" FROM pg_catalog.pg_namespace " + " WHERE nspname <> 'pg_toast' AND (nspname !~ '^pg_temp_' " + " OR nspname = (pg_catalog.current_schemas(true))[1]) AND (nspname !~ '^pg_toast_temp_' " + " OR nspname = replace((pg_catalog.current_schemas(true))[1], 'pg_temp_', 'pg_toast_temp_')) "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schemaPattern != null) { sql += " AND nspname LIKE " + escapeQuotes(schemaPattern); } if (connection.getHideUnprivilegedObjects()) { @@ -1692,8 +1708,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { sql = ""; } - sql += "SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull " - + "OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,t.typtypmod,"; + sql += "SELECT current_database(), n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull " + + " OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,t.typtypmod,"; if (connection.haveMinimumServerVersion(ServerVersion.v8_4)) { sql += "row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, "; @@ -1724,16 +1740,19 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') " + " WHERE c.relkind in ('r','p','v','f','m') and a.attnum > 0 AND NOT a.attisdropped "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (tableNamePattern != null && !tableNamePattern.isEmpty()) { + if (tableNamePattern != null) { sql += " AND c.relname LIKE " + escapeQuotes(tableNamePattern); } if (connection.haveMinimumServerVersion(ServerVersion.v8_4)) { sql += ") c WHERE true "; } - if (columnNamePattern != null && !columnNamePattern.isEmpty()) { + if (columnNamePattern != null) { sql += " AND attname LIKE " + escapeQuotes(columnNamePattern); } sql += " ORDER BY nspname,c.relname,attnum "; @@ -1745,8 +1764,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { int typeOid = (int) rs.getLong("atttypid"); int typeMod = rs.getInt("atttypmod"); - tuple[0] = null; // Catalog name, not supported - tuple[1] = rs.getBytes("nspname"); // Schema + tuple[0] = rs.getBytes("current_database"); // Catalog (database) name + tuple[1] = rs.getBytes("nspname"); // Schema name tuple[2] = rs.getBytes("relname"); // Table name tuple[3] = rs.getBytes("attname"); // Column name @@ -1885,7 +1904,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { f[7] = new Field("IS_GRANTABLE", Oid.VARCHAR); String sql; - sql = "SELECT n.nspname,c.relname,r.rolname,c.relacl, " + sql = "SELECT current_database(), n.nspname,c.relname,r.rolname,c.relacl, " + (connection.haveMinimumServerVersion(ServerVersion.v8_4) ? "a.attacl, " : "") + " a.attname " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, " @@ -1896,13 +1915,16 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " AND c.relkind = 'r' " + " AND a.attnum > 0 AND NOT a.attisdropped "; - if (schema != null && !schema.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schema != null) { sql += " AND n.nspname = " + escapeQuotes(schema); } - if (table != null && !table.isEmpty()) { + if (table != null) { sql += " AND c.relname = " + escapeQuotes(table); } - if (columnNamePattern != null && !columnNamePattern.isEmpty()) { + if (columnNamePattern != null) { sql += " AND a.attname LIKE " + escapeQuotes(columnNamePattern); } sql += " ORDER BY attname "; @@ -1910,6 +1932,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { + byte[] catalogName = rs.getBytes("current_database"); byte[] schemaName = rs.getBytes("nspname"); byte[] tableName = rs.getBytes("relname"); byte[] column = rs.getBytes("attname"); @@ -1935,7 +1958,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { for (@Nullable String[] grants : grantor) { String grantable = owner.equals(grantee) ? "YES" : grants[1]; byte[] @Nullable [] tuple = new byte[8][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schemaName; tuple[2] = tableName; tuple[3] = column; @@ -1970,17 +1993,21 @@ public class PgDatabaseMetaData implements DatabaseMetaData { String sql; // r = ordinary table, p = partitioned table, v = view, m = materialized view, f = foreign table - sql = "SELECT n.nspname,c.relname,r.rolname,c.relacl " + sql = "SELECT current_database(), n.nspname,c.relname,r.rolname,c.relacl " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_roles r " + " WHERE c.relnamespace = n.oid " + " AND c.relowner = r.oid " + " AND c.relkind IN ('r','p','v','m','f') "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (tableNamePattern != null && !tableNamePattern.isEmpty()) { + if (tableNamePattern != null) { sql += " AND c.relname LIKE " + escapeQuotes(tableNamePattern); } sql += " ORDER BY nspname, relname "; @@ -1988,6 +2015,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { + byte[] catalogName = rs.getBytes("current_database"); byte[] schema = rs.getBytes("nspname"); byte[] table = rs.getBytes("relname"); String owner = castNonNull(rs.getString("rolname")); @@ -2007,7 +2035,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { // owner always has grant privileges String grantable = owner.equals(granteeUser) ? "YES" : grantTuple[1]; byte[] @Nullable [] tuple = new byte[7][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = table; tuple[3] = connection.encodeString(grantor); @@ -2221,7 +2249,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " ON (a.attnum = (i.keys).x AND a.attrelid = i.indrelid) " + "WHERE true "; - if (schema != null && !schema.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schema != null) { sql += " AND n.nspname = " + escapeQuotes(schema); } @@ -2312,7 +2344,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { // when returning primary keys. String keyCountColumn = connection.haveMinimumServerVersion(ServerVersion.v11) ? "i.indnkeyatts" : "i.indnatts"; - sql = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, " + sql = "SELECT current_database() AS TABLE_CAT, n.nspname AS TABLE_SCHEM, " + " ct.relname AS TABLE_NAME, a.attname AS COLUMN_NAME, " + " (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, ci.relname AS PK_NAME, " + " information_schema._pg_expandarray(i.indkey) AS KEYS, a.attnum AS A_ATTNUM, " @@ -2324,11 +2356,15 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) " + "WHERE true "; - if (schema != null && !schema.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schema != null) { sql += " AND n.nspname = " + escapeQuotes(schema); } - if (table != null && !table.isEmpty()) { + if (table != null) { sql += " AND ct.relname = " + escapeQuotes(table); } @@ -2425,8 +2461,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { */ String sql = - "SELECT NULL::text AS \"PKTABLE_CAT\", pkn.nspname AS \"PKTABLE_SCHEM\", pkc.relname AS \"PKTABLE_NAME\", pka.attname AS \"PKCOLUMN_NAME\", " - + "NULL::text AS \"FKTABLE_CAT\", fkn.nspname AS \"FKTABLE_SCHEM\", fkc.relname AS \"FKTABLE_NAME\", fka.attname AS \"FKCOLUMN_NAME\", " + "SELECT current_database() AS \"PKTABLE_CAT\", pkn.nspname AS \"PKTABLE_SCHEM\", pkc.relname AS \"PKTABLE_NAME\", pka.attname AS \"PKCOLUMN_NAME\", " + + "current_database() AS \"FKTABLE_CAT\", fkn.nspname AS \"FKTABLE_SCHEM\", fkc.relname AS \"FKTABLE_NAME\", fka.attname AS \"FKCOLUMN_NAME\", " + "pos.n AS \"KEY_SEQ\", " + "CASE con.confupdtype " + " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade @@ -2485,16 +2521,22 @@ public class PgDatabaseMetaData implements DatabaseMetaData { sql += " AND pkic.oid = con.conindid "; } - if (primarySchema != null && !primarySchema.isEmpty()) { + if (primaryCatalog != null) { + sql += " AND current_database() = " + escapeQuotes(primaryCatalog); + } + if (primarySchema != null) { sql += " AND pkn.nspname = " + escapeQuotes(primarySchema); } - if (foreignSchema != null && !foreignSchema.isEmpty()) { + if (foreignCatalog != null) { + sql += " AND current_database() = " + escapeQuotes(foreignCatalog); + } + if (foreignSchema != null) { sql += " AND fkn.nspname = " + escapeQuotes(foreignSchema); } - if (primaryTable != null && !primaryTable.isEmpty()) { + if (primaryTable != null) { sql += " AND pkc.relname = " + escapeQuotes(primaryTable); } - if (foreignTable != null && !foreignTable.isEmpty()) { + if (foreignTable != null) { sql += " AND fkc.relname = " + escapeQuotes(foreignTable); } @@ -2673,7 +2715,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { */ String sql; if (connection.haveMinimumServerVersion(ServerVersion.v8_3)) { - sql = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, " + sql = "SELECT current_database() AS TABLE_CAT, " + + " n.nspname AS TABLE_SCHEM, " + " ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, " + " NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, " + " CASE i.indisclustered " @@ -2697,7 +2740,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData { + " JOIN pg_catalog.pg_am am ON (ci.relam = am.oid) " + "WHERE true "; - if (schema != null && !schema.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schema != null) { sql += " AND n.nspname = " + escapeQuotes(schema); } @@ -2743,13 +2790,18 @@ public class PgDatabaseMetaData implements DatabaseMetaData { String from; String where; - select = "SELECT NULL AS \"TABLE_CAT\", n.nspname AS \"TABLE_SCHEM\", "; + select = "SELECT current_database() AS \"TABLE_CAT\", " + + " n.nspname AS \"TABLE_SCHEM\", "; from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, " + " pg_catalog.pg_attribute a, pg_catalog.pg_am am "; where = " AND n.oid = ct.relnamespace "; from += ", pg_catalog.pg_index i "; - if (schema != null && !schema.isEmpty()) { + if (catalog != null) { + where += " AND current_database() = " + escapeQuotes(catalog); + } + + if (schema != null) { where += " AND n.nspname = " + escapeQuotes(schema); } @@ -2865,11 +2917,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData { public ResultSet getUDTs(@Nullable String catalog, @Nullable String schemaPattern, @Nullable String typeNamePattern, int @Nullable [] types) throws SQLException { String sql = "select " - + "null as type_cat, n.nspname as type_schem, t.typname as type_name, null as class_name, " + + "current_database() as \"TYPE_CAT\", n.nspname as \"TYPE_SCHEM\", t.typname as \"TYPE_NAME\", null as \"CLASS_NAME\", " + "CASE WHEN t.typtype='c' then " + Types.STRUCT + " else " + Types.DISTINCT - + " end as data_type, pg_catalog.obj_description(t.oid, 'pg_type') " - + "as remarks, CASE WHEN t.typtype = 'd' then (select CASE"; + + " end as \"DATA_TYPE\", pg_catalog.obj_description(t.oid, 'pg_type') " + + "as \"REMARKS\", CASE WHEN t.typtype = 'd' then (select CASE"; TypeInfo typeInfo = connection.getTypeInfo(); StringBuilder sqlwhen = new StringBuilder(); @@ -2886,7 +2938,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { sql += sqlwhen.toString(); sql += " else " + Types.OTHER + " end from pg_type base_type where base_type.oid=t.typbasetype) " - + "else null end as base_type " + + "else null end as \"BASE_TYPE\" " + "from pg_catalog.pg_type t, pg_catalog.pg_namespace n where t.typnamespace = n.oid and n.nspname != 'pg_catalog' and n.nspname != 'pg_toast'"; StringBuilder toAdd = new StringBuilder(); @@ -2926,6 +2978,10 @@ public class PgDatabaseMetaData implements DatabaseMetaData { toAdd.append(" and t.typname like ").append(escapeQuotes(typeNamePattern)); } + if (catalog != null) { + toAdd.append(" and current_database() = ").append(escapeQuotes(catalog)); + } + // schemaPattern may have been modified above if (schemaPattern != null) { toAdd.append(" and n.nspname like ").append(escapeQuotes(schemaPattern)); @@ -2937,7 +2993,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { sql += " AND has_type_privilege(t.oid, 'USAGE')"; } - sql += " order by data_type, type_schem, type_name"; + sql += " order by \"DATA_TYPE\", \"TYPE_SCHEM\", \"TYPE_NAME\" "; return createMetaDataStatement().executeQuery(sql); } @@ -3034,7 +3090,8 @@ public class PgDatabaseMetaData implements DatabaseMetaData { // Build query and result String sql; - sql = "SELECT current_database() AS \"FUNCTION_CAT\", n.nspname AS \"FUNCTION_SCHEM\", p.proname AS \"FUNCTION_NAME\", " + sql = "SELECT current_database() AS \"FUNCTION_CAT\", " + + " n.nspname AS \"FUNCTION_SCHEM\", p.proname AS \"FUNCTION_NAME\", " + " d.description AS \"REMARKS\", " + funcTypeSql + " AS \"FUNCTION_TYPE\", " + " p.proname || '_' || p.oid AS \"SPECIFIC_NAME\" " @@ -3046,13 +3103,16 @@ public class PgDatabaseMetaData implements DatabaseMetaData { if (connection.haveMinimumServerVersion(ServerVersion.v11)) { sql += " AND p.prokind='f'"; } + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } /* if the user provides a schema then search inside the schema for it */ - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (functionNamePattern != null && !functionNamePattern.isEmpty()) { + if (functionNamePattern != null) { sql += " AND p.proname LIKE " + escapeQuotes(functionNamePattern); } if (connection.getHideUnprivilegedObjects()) { @@ -3091,14 +3151,17 @@ public class PgDatabaseMetaData implements DatabaseMetaData { f[16] = new Field("SPECIFIC_NAME", Oid.VARCHAR); String sql; - sql = "SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid, " + sql = "SELECT current_database(), n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid, " + " p.proargnames, p.proargmodes, p.proallargtypes, p.oid " + " FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_type t " + " WHERE p.pronamespace=n.oid AND p.prorettype=t.oid "; - if (schemaPattern != null && !schemaPattern.isEmpty()) { + if (catalog != null) { + sql += " AND current_database() = " + escapeQuotes(catalog); + } + if (schemaPattern != null) { sql += " AND n.nspname LIKE " + escapeQuotes(schemaPattern); } - if (functionNamePattern != null && !functionNamePattern.isEmpty()) { + if (functionNamePattern != null) { sql += " AND p.proname LIKE " + escapeQuotes(functionNamePattern); } sql += " ORDER BY n.nspname, p.proname, p.oid::text "; @@ -3108,6 +3171,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { + byte[] catalogName = rs.getBytes("current_database"); byte[] schema = rs.getBytes("nspname"); byte[] functionName = rs.getBytes("proname"); byte[] specificName = @@ -3148,7 +3212,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { if ("b".equals(returnTypeType) || "d".equals(returnTypeType) || "e".equals(returnTypeType) || ("p".equals(returnTypeType) && argModesArray == null)) { byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = functionName; tuple[3] = connection.encodeString("returnValue"); @@ -3174,7 +3238,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { // Add a row for each argument. for (int i = 0; i < numArgs; i++) { byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = functionName; @@ -3231,7 +3295,7 @@ public class PgDatabaseMetaData implements DatabaseMetaData { while (columnrs.next()) { int columnTypeOid = (int) columnrs.getLong("atttypid"); byte[] @Nullable [] tuple = new byte[columns][]; - tuple[0] = null; + tuple[0] = catalogName; tuple[1] = schema; tuple[2] = functionName; tuple[3] = columnrs.getBytes("attname"); diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java index d90dd461..58d6c0aa 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java @@ -256,6 +256,21 @@ public class DatabaseMetaDataTest { } } + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void tables_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws Exception { + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getTables("", "", "metadatates%", new String[]{"TABLE"}); + assertFalse(rs.next()); + rs.close(); + + rs = dbmd.getColumns("", "", "meta%", "%"); + assertFalse(rs.next()); + rs.close(); + } + @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void tables(BinaryMode binaryMode) throws Exception { @@ -280,7 +295,7 @@ public class DatabaseMetaDataTest { rs.close(); - rs = dbmd.getColumns("", "", "meta%", "%"); + rs = dbmd.getColumns(null, null, "meta%", "%"); assertTrue(rs.next()); assertEquals("metadatatest", rs.getString("TABLE_NAME")); assertEquals("id", rs.getString("COLUMN_NAME")); @@ -297,262 +312,398 @@ public class DatabaseMetaDataTest { assertEquals(Types.TIMESTAMP, rs.getInt("DATA_TYPE")); } + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void crossReference_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); + + TestUtil.createTable(con1, "ww", + "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); + + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getCrossReference("", "", "vv", null, null, "ww"); + assertFalse(rs.next()); + + TestUtil.dropTable(con1, "vv"); + TestUtil.dropTable(con1, "ww"); + } + } + @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void crossReference(BinaryMode binaryMode) throws Exception { - Connection con1 = TestUtil.openDB(); + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); - TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); + TestUtil.createTable(con1, "ww", + "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); - TestUtil.createTable(con1, "ww", - "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); + ResultSet rs = dbmd.getCrossReference(null, null, "vv", null, null, "ww"); + String[] expectedPkColumnNames = new String[]{"a", "b"}; + String[] expectedFkColumnNames = new String[]{"m", "n"}; + int numRows = 0; - ResultSet rs = dbmd.getCrossReference(null, "", "vv", null, "", "ww"); - String[] expectedPkColumnNames = new String[]{"a", "b"}; - String[] expectedFkColumnNames = new String[]{"m", "n"}; - int numRows = 0; + for (int j = 1; rs.next(); j++) { - for (int j = 1; rs.next(); j++) { + String pkTableName = rs.getString("PKTABLE_NAME"); + assertEquals("vv", pkTableName); - String pkTableName = rs.getString("PKTABLE_NAME"); - assertEquals("vv", pkTableName); + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertEquals(expectedPkColumnNames[j - 1], pkColumnName); - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertEquals(expectedPkColumnNames[j - 1], pkColumnName); + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("ww", fkTableName); - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("ww", fkTableName); + String fkColumnName = rs.getString("FKCOLUMN_NAME"); + assertEquals(expectedFkColumnNames[j - 1], fkColumnName); - String fkColumnName = rs.getString("FKCOLUMN_NAME"); - assertEquals(expectedFkColumnNames[j - 1], fkColumnName); + String fkName = rs.getString("FK_NAME"); + assertEquals("ww_m_fkey", fkName); - String fkName = rs.getString("FK_NAME"); - assertEquals("ww_m_fkey", fkName); + String pkName = rs.getString("PK_NAME"); + assertEquals("vv_pkey", pkName); - String pkName = rs.getString("PK_NAME"); - assertEquals("vv_pkey", pkName); + int keySeq = rs.getInt("KEY_SEQ"); + assertEquals(j, keySeq); + numRows += 1; + } + assertEquals(2, numRows); - int keySeq = rs.getInt("KEY_SEQ"); - assertEquals(j, keySeq); - numRows += 1; + TestUtil.dropTable(con1, "vv"); + TestUtil.dropTable(con1, "ww"); } - assertEquals(2, numRows); + } - TestUtil.dropTable(con1, "vv"); - TestUtil.dropTable(con1, "ww"); - TestUtil.closeDB(con1); + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void foreignKeyActions_whenSchemaArgEmpty_expectNoResults(BinaryMode binaryMode) throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "pkt", "id int primary key"); + TestUtil.createTable(conn, "fkt1", + "id int references pkt on update restrict on delete cascade"); + TestUtil.createTable(conn, "fkt2", + "id int references pkt on update set null on delete set default"); + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getImportedKeys(null, "", "fkt1"); + assertFalse(rs.next()); + rs.close(); + + rs = dbmd.getImportedKeys(null, "", "fkt2"); + assertFalse(rs.next()); + rs.close(); + + TestUtil.dropTable(conn, "fkt2"); + TestUtil.dropTable(conn, "fkt1"); + TestUtil.dropTable(conn, "pkt"); + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void foreignKeyActions(BinaryMode binaryMode) throws Exception { - Connection conn = TestUtil.openDB(); - TestUtil.createTable(conn, "pkt", "id int primary key"); - TestUtil.createTable(conn, "fkt1", - "id int references pkt on update restrict on delete cascade"); - TestUtil.createTable(conn, "fkt2", - "id int references pkt on update set null on delete set default"); - DatabaseMetaData dbmd = conn.getMetaData(); - - ResultSet rs = dbmd.getImportedKeys(null, "", "fkt1"); - assertTrue(rs.next()); - assertEquals(DatabaseMetaData.importedKeyRestrict, rs.getInt("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyCascade, rs.getInt("DELETE_RULE")); - rs.close(); + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "pkt", "id int primary key"); + TestUtil.createTable(conn, "fkt1", + "id int references pkt on update restrict on delete cascade"); + TestUtil.createTable(conn, "fkt2", + "id int references pkt on update set null on delete set default"); + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt1"); + assertTrue(rs.next()); + assertEquals(DatabaseMetaData.importedKeyRestrict, rs.getInt("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyCascade, rs.getInt("DELETE_RULE")); + rs.close(); - rs = dbmd.getImportedKeys(null, "", "fkt2"); - assertTrue(rs.next()); - assertEquals(DatabaseMetaData.importedKeySetNull, rs.getInt("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeySetDefault, rs.getInt("DELETE_RULE")); - rs.close(); + rs = dbmd.getImportedKeys(null, null, "fkt2"); + assertTrue(rs.next()); + assertEquals(DatabaseMetaData.importedKeySetNull, rs.getInt("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeySetDefault, rs.getInt("DELETE_RULE")); + rs.close(); + + TestUtil.dropTable(conn, "fkt2"); + TestUtil.dropTable(conn, "fkt1"); + TestUtil.dropTable(conn, "pkt"); + } + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void foreignKeysToUniqueIndexes_whenCatalogAndSchemaArgsEmpty_expect(BinaryMode binaryMode) throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); + + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); + assertFalse(rs.next()); - TestUtil.dropTable(conn, "fkt2"); - TestUtil.dropTable(conn, "fkt1"); - TestUtil.dropTable(conn, "pkt"); - TestUtil.closeDB(conn); + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void foreignKeysToUniqueIndexes(BinaryMode binaryMode) throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "pkt", - "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); - TestUtil.createTable(con1, "fkt", - "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); - DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); - int j = 0; - for (; rs.next(); j++) { - assertEquals("pkt", rs.getString("PKTABLE_NAME")); - assertEquals("fkt", rs.getString("FKTABLE_NAME")); - assertEquals("pkt_un_b", rs.getString("PK_NAME")); - assertEquals("b", rs.getString("PKCOLUMN_NAME")); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt"); + int j = 0; + for (; rs.next(); j++) { + assertEquals("pkt", rs.getString("PKTABLE_NAME")); + assertEquals("fkt", rs.getString("FKTABLE_NAME")); + assertEquals("pkt_un_b", rs.getString("PK_NAME")); + assertEquals("b", rs.getString("PKCOLUMN_NAME")); + } + assertEquals(1, j); + + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); } - assertEquals(1, j); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void multiColumnForeignKeys_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); - TestUtil.dropTable(con1, "fkt"); - TestUtil.dropTable(con1, "pkt"); - con1.close(); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); + assertFalse(rs.next()); + + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void multiColumnForeignKeys(BinaryMode binaryMode) throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "pkt", - "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); - TestUtil.createTable(con1, "fkt", - "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); - DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); - int j = 0; - for (; rs.next(); j++) { - assertEquals("pkt", rs.getString("PKTABLE_NAME")); - assertEquals("fkt", rs.getString("FKTABLE_NAME")); - assertEquals(j + 1, rs.getInt("KEY_SEQ")); - if (j == 0) { - assertEquals("b", rs.getString("PKCOLUMN_NAME")); - assertEquals("c", rs.getString("FKCOLUMN_NAME")); - } else { - assertEquals("a", rs.getString("PKCOLUMN_NAME")); - assertEquals("d", rs.getString("FKCOLUMN_NAME")); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt"); + int j = 0; + for (; rs.next(); j++) { + assertEquals("pkt", rs.getString("PKTABLE_NAME")); + assertEquals("fkt", rs.getString("FKTABLE_NAME")); + assertEquals(j + 1, rs.getInt("KEY_SEQ")); + if (j == 0) { + assertEquals("b", rs.getString("PKCOLUMN_NAME")); + assertEquals("c", rs.getString("FKCOLUMN_NAME")); + } else { + assertEquals("a", rs.getString("PKCOLUMN_NAME")); + assertEquals("d", rs.getString("FKCOLUMN_NAME")); + } } + assertEquals(2, j); + + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); } - assertEquals(2, j); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void sameTableForeignKeys_whenSchemaArgEmpty_expectNoResults(BinaryMode binaryMode) throws Exception { + try (Connection con1 = TestUtil.openDB()) { + + TestUtil.createTable(con1, "person", + "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," + + "FIRST_NAME_PARENT_1 character varying(100)," + + "LAST_NAME_PARENT_1 character varying(100)," + + "FIRST_NAME_PARENT_2 character varying(100)," + + "LAST_NAME_PARENT_2 character varying(100)," + + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," + + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE," + + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE"); + + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + ResultSet rs = dbmd.getImportedKeys(null, "", "person"); + assertFalse(rs.next()); - TestUtil.dropTable(con1, "fkt"); - TestUtil.dropTable(con1, "pkt"); - con1.close(); + TestUtil.dropTable(con1, "person"); + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void sameTableForeignKeys(BinaryMode binaryMode) throws Exception { - Connection con1 = TestUtil.openDB(); - - TestUtil.createTable(con1, "person", - "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," - + "FIRST_NAME_PARENT_1 character varying(100)," - + "LAST_NAME_PARENT_1 character varying(100)," - + "FIRST_NAME_PARENT_2 character varying(100)," - + "LAST_NAME_PARENT_2 character varying(100)," - + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," - + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" - + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " - + "ON UPDATE CASCADE ON DELETE CASCADE," - + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" - + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " - + "ON UPDATE CASCADE ON DELETE CASCADE"); + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "person", + "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," + + "FIRST_NAME_PARENT_1 character varying(100)," + + "LAST_NAME_PARENT_1 character varying(100)," + + "FIRST_NAME_PARENT_2 character varying(100)," + + "LAST_NAME_PARENT_2 character varying(100)," + + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," + + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE," + + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE"); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); - ResultSet rs = dbmd.getImportedKeys(null, "", "person"); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + ResultSet rs = dbmd.getImportedKeys(null, null, "person"); - final List<String> fkNames = new ArrayList<>(); + final List<String> fkNames = new ArrayList<>(); - int lastFieldCount = -1; - while (rs.next()) { - // destination table (all foreign keys point to the same) - String pkTableName = rs.getString("PKTABLE_NAME"); - assertEquals("person", pkTableName); - - // destination fields - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertTrue("first_name".equals(pkColumnName) || "last_name".equals(pkColumnName)); - - // source table (all foreign keys are in the same) - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("person", fkTableName); - - // foreign key name - String fkName = rs.getString("FK_NAME"); - // sequence number within the foreign key - int seq = rs.getInt("KEY_SEQ"); - if (seq == 1) { - // begin new foreign key - assertFalse(fkNames.contains(fkName)); - fkNames.add(fkName); - // all foreign keys have 2 fields - assertTrue(lastFieldCount < 0 || lastFieldCount == 2); - } else { - // continue foreign key, i.e. fkName matches the last foreign key - assertEquals(fkNames.get(fkNames.size() - 1), fkName); - // see always increases by 1 - assertEquals(seq, lastFieldCount + 1); + int lastFieldCount = -1; + while (rs.next()) { + // destination table (all foreign keys point to the same) + String pkTableName = rs.getString("PKTABLE_NAME"); + assertEquals("person", pkTableName); + + // destination fields + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertTrue("first_name".equals(pkColumnName) || "last_name".equals(pkColumnName)); + + // source table (all foreign keys are in the same) + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("person", fkTableName); + + // foreign key name + String fkName = rs.getString("FK_NAME"); + // sequence number within the foreign key + int seq = rs.getInt("KEY_SEQ"); + if (seq == 1) { + // begin new foreign key + assertFalse(fkNames.contains(fkName)); + fkNames.add(fkName); + // all foreign keys have 2 fields + assertTrue(lastFieldCount < 0 || lastFieldCount == 2); + } else { + // continue foreign key, i.e. fkName matches the last foreign key + assertEquals(fkNames.get(fkNames.size() - 1), fkName); + // see always increases by 1 + assertEquals(seq, lastFieldCount + 1); + } + lastFieldCount = seq; } - lastFieldCount = seq; + // there's more than one foreign key from a table to another + assertEquals(2, fkNames.size()); + + TestUtil.dropTable(con1, "person"); } - // there's more than one foreign key from a table to another - assertEquals(2, fkNames.size()); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void foreignKeys_whenSchemaArgNull_expectNoResults(BinaryMode binaryMode) throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "people", "id int4 primary key, name text"); + TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); + + TestUtil.createTable(con1, "users", + "id int4 primary key, people_id int4, policy_id int4," + + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," + + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); + + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getImportedKeys(null, "", "users"); + assertFalse(rs.next()); + rs.close(); - TestUtil.dropTable(con1, "person"); - TestUtil.closeDB(con1); + rs = dbmd.getExportedKeys(null, "", "people"); + assertFalse(rs.next()); + rs.close(); + + TestUtil.dropTable(con1, "users"); + TestUtil.dropTable(con1, "people"); + TestUtil.dropTable(con1, "policy"); + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void foreignKeys(BinaryMode binaryMode) throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "people", "id int4 primary key, name text"); - TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "people", "id int4 primary key, name text"); + TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); - TestUtil.createTable(con1, "users", - "id int4 primary key, people_id int4, policy_id int4," - + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," - + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); + TestUtil.createTable(con1, "users", + "id int4 primary key, people_id int4, policy_id int4," + + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," + + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); - ResultSet rs = dbmd.getImportedKeys(null, "", "users"); - int j = 0; - for (; rs.next(); j++) { + ResultSet rs = dbmd.getImportedKeys(null, null, "users"); + int j = 0; + for (; rs.next(); j++) { - String pkTableName = rs.getString("PKTABLE_NAME"); - assertTrue("people".equals(pkTableName) || "policy".equals(pkTableName)); + String pkTableName = rs.getString("PKTABLE_NAME"); + assertTrue("people".equals(pkTableName) || "policy".equals(pkTableName)); - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertEquals("id", pkColumnName); + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertEquals("id", pkColumnName); - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("users", fkTableName); + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("users", fkTableName); - String fkColumnName = rs.getString("FKCOLUMN_NAME"); - assertTrue("people_id".equals(fkColumnName) || "policy_id".equals(fkColumnName)); + String fkColumnName = rs.getString("FKCOLUMN_NAME"); + assertTrue("people_id".equals(fkColumnName) || "policy_id".equals(fkColumnName)); - String fkName = rs.getString("FK_NAME"); - assertTrue(fkName.startsWith("people") || fkName.startsWith("policy")); + String fkName = rs.getString("FK_NAME"); + assertTrue(fkName.startsWith("people") || fkName.startsWith("policy")); - String pkName = rs.getString("PK_NAME"); - assertTrue("people_pkey".equals(pkName) || "policy_pkey".equals(pkName)); + String pkName = rs.getString("PK_NAME"); + assertTrue("people_pkey".equals(pkName) || "policy_pkey".equals(pkName)); - } + } - assertEquals(2, j); + assertEquals(2, j); - rs = dbmd.getExportedKeys(null, "", "people"); + rs = dbmd.getExportedKeys(null, null, "people"); - // this is hacky, but it will serve the purpose - assertTrue(rs.next()); + // this is hacky, but it will serve the purpose + assertTrue(rs.next()); - assertEquals("people", rs.getString("PKTABLE_NAME")); - assertEquals("id", rs.getString("PKCOLUMN_NAME")); + assertEquals("people", rs.getString("PKTABLE_NAME")); + assertEquals("id", rs.getString("PKCOLUMN_NAME")); - assertEquals("users", rs.getString("FKTABLE_NAME")); - assertEquals("people_id", rs.getString("FKCOLUMN_NAME")); + assertEquals("users", rs.getString("FKTABLE_NAME")); + assertEquals("people_id", rs.getString("FKCOLUMN_NAME")); - assertTrue(rs.getString("FK_NAME").startsWith("people")); + assertTrue(rs.getString("FK_NAME").startsWith("people")); - TestUtil.dropTable(con1, "users"); - TestUtil.dropTable(con1, "people"); - TestUtil.dropTable(con1, "policy"); - TestUtil.closeDB(con1); + TestUtil.dropTable(con1, "users"); + TestUtil.dropTable(con1, "people"); + TestUtil.dropTable(con1, "policy"); + } } @MethodSource("data") @@ -849,9 +1000,17 @@ public class DatabaseMetaDataTest { @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void notNullDomainColumn(BinaryMode binaryMode) throws SQLException { + void notNullDomainColumn_whenCatalogAndSchemaAndColumnNameArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "domaintable", ""); + assertFalse(rs.next()); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void notNullDomainColumn(BinaryMode binaryMode) throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "domaintable", null); assertTrue(rs.next()); assertEquals("id", rs.getString("COLUMN_NAME")); assertEquals("NO", rs.getString("IS_NULLABLE")); @@ -862,9 +1021,17 @@ public class DatabaseMetaDataTest { @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void domainColumnSize(BinaryMode binaryMode) throws SQLException { + void domainColumnSize_whenCatalogAndSchemaAndColumnNameArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "domaintable", ""); + assertFalse(rs.next()); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void domainColumnSize(BinaryMode binaryMode) throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "domaintable", null); assertTrue(rs.next()); assertEquals("id", rs.getString("COLUMN_NAME")); assertEquals(10, rs.getInt("COLUMN_SIZE")); @@ -1218,7 +1385,7 @@ public class DatabaseMetaDataTest { assertEquals("jdbc", schema, "schema name "); // now test to see if the fully qualified stuff works as planned - rs = dbmd.getUDTs("catalog", "public", "catalog.jdbc.testint8", null); + rs = dbmd.getUDTs(System.getProperty("database"), "public", "catalog.jdbc.testint8", null); assertTrue(rs.next()); cat = rs.getString("type_cat"); schema = rs.getString("type_schem"); @@ -1465,9 +1632,17 @@ public class DatabaseMetaDataTest { @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void informationAboutArrayTypes(BinaryMode binaryMode) throws SQLException { + void informationAboutArrayTypes_whenCatalogSchemaColumnNamePatternArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "arraytable", ""); + assertFalse(rs.next()); + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void informationAboutArrayTypes(BinaryMode binaryMode) throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "arraytable", null); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(5, rs.getInt("COLUMN_SIZE")); @@ -1480,11 +1655,22 @@ public class DatabaseMetaDataTest { @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void primaryKeysWithIncludeColumns(BinaryMode binaryMode) throws SQLException { + void primaryKeysWithIncludeColumns_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { String tableName = "pk_include_column"; if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getPrimaryKeys("", "", tableName); + assertFalse(rs.next()); + } + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void primaryKeysWithIncludeColumns(BinaryMode binaryMode) throws SQLException { + String tableName = "pk_include_column"; + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getPrimaryKeys(null, null, tableName); // getPrimaryKeys should return only the key columns and not the included column assertTrue(rs.next()); @@ -1500,76 +1686,109 @@ public class DatabaseMetaDataTest { @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void partitionedTablesIndex(BinaryMode binaryMode) throws SQLException { + void partitionedTablesIndex_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { - Statement stmt = null; - try { - stmt = con.createStatement(); + try (Statement stmt = con.createStatement()) { stmt.execute( "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getPrimaryKeys("", "", "measurement"); + assertFalse(rs.next()); + + stmt.execute("drop table if exists measurement"); + } + } + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void partitionedTablesIndex(BinaryMode binaryMode) throws SQLException { + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + try (Statement stmt = con.createStatement()) { + stmt.execute( + "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getPrimaryKeys(null, null, "measurement"); assertTrue(rs.next()); assertEquals("measurement_pkey", rs.getString(6)); - } finally { - if (stmt != null) { - stmt.execute("drop table if exists measurement"); - stmt.close(); - } + stmt.execute("drop table if exists measurement"); } } + } + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void partitionedTables_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + try (Statement stmt = con.createStatement()) { + stmt.execute( + "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getTables("", "", "measurement", new String[]{"PARTITIONED TABLE"}); + assertFalse(rs.next()); + rs.close(); + rs = dbmd.getPrimaryKeys("", "", "measurement"); + assertFalse(rs.next()); + + stmt.execute("drop table if exists measurement"); + } + } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") void partitionedTables(BinaryMode binaryMode) throws SQLException { if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { - Statement stmt = null; - try { - stmt = con.createStatement(); + try (Statement stmt = con.createStatement()) { stmt.execute( "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getTables("", "", "measurement", new String[]{"PARTITIONED TABLE"}); + ResultSet rs = dbmd.getTables(null, null, "measurement", new String[]{"PARTITIONED TABLE"}); assertTrue(rs.next()); assertEquals("measurement", rs.getString("table_name")); rs.close(); - rs = dbmd.getPrimaryKeys("", "", "measurement"); + rs = dbmd.getPrimaryKeys(null, null, "measurement"); assertTrue(rs.next()); assertEquals("measurement_pkey", rs.getString(6)); - } finally { - if (stmt != null) { - stmt.execute("drop table if exists measurement"); - stmt.close(); - } + stmt.execute("drop table if exists measurement"); } } } @MethodSource("data") @ParameterizedTest(name = "binary = {0}") - void identityColumns(BinaryMode binaryMode) throws SQLException { + void identityColumns_whenCatalogAndSchemaArgsEmpty_expectNoResults(BinaryMode binaryMode) throws SQLException { if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v10) ) { - Statement stmt = null; - try { - stmt = con.createStatement(); + try (Statement stmt = con.createStatement()) { stmt.execute("CREATE TABLE test_new (" + "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + "payload text)"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "test_new", "id"); + assertFalse(rs.next()); + + stmt.execute("drop table test_new"); + } + } + } + + @MethodSource("data") + @ParameterizedTest(name = "binary = {0}") + void identityColumns(BinaryMode binaryMode) throws SQLException { + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v10) ) { + try (Statement stmt = con.createStatement()) { + stmt.execute("CREATE TABLE test_new (" + + "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + + "payload text)"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "test_new", "id"); assertTrue(rs.next()); assertEquals("id", rs.getString("COLUMN_NAME")); assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); - } finally { - if ( stmt != null ) { - stmt.execute("drop table test_new"); - stmt.close(); - } + stmt.execute("drop table test_new"); } } } @@ -1579,7 +1798,7 @@ public class DatabaseMetaDataTest { void generatedColumns(BinaryMode binaryMode) throws SQLException { if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v12) ) { DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getColumns("", "", "employee", "gross_pay"); + ResultSet rs = dbmd.getColumns(null, null, "employee", "gross_pay"); assertTrue(rs.next()); assertEquals("gross_pay", rs.getString("COLUMN_NAME")); assertTrue(rs.getBoolean("IS_GENERATEDCOLUMN")); @@ -1688,7 +1907,7 @@ public class DatabaseMetaDataTest { assertEquals("SPECIFIC_NAME", rsmd.getColumnName(17)); assertTrue(rs.next()); - assertNull(rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("returnValue", rs.getString(4)); @@ -1698,7 +1917,7 @@ public class DatabaseMetaDataTest { assertEquals(0, rs.getInt(15)); assertTrue(rs.next()); - assertNull(rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("$1", rs.getString(4)); @@ -1708,7 +1927,7 @@ public class DatabaseMetaDataTest { assertEquals(1, rs.getInt(15)); assertTrue(rs.next()); - assertNull(rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("$2", rs.getString(4)); diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java index 05c5b3d0..03411916 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java @@ -42,10 +42,18 @@ class DatabaseMetaDataTest { } @Test - void getColumnsForDomain() throws Exception { + void getColumnsForDomain_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "domtab", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForDomain() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "domtab", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(Types.DISTINCT, rs.getInt("DATA_TYPE")); diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java index 80e16e4a..70424fe7 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java @@ -7,11 +7,10 @@ package org.postgresql.test.jdbc4; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.ServerVersion; @@ -30,6 +29,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; class DatabaseMetaDataTest { @@ -78,10 +78,18 @@ class DatabaseMetaDataTest { } @Test - void getColumnsForAutoIncrement() throws Exception { + void getColumnsForAutoIncrement_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "sercoltest", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForAutoIncrement() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "sercoltest", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals("YES", rs.getString("IS_AUTOINCREMENT")); @@ -93,42 +101,105 @@ class DatabaseMetaDataTest { assertFalse(rs.next()); } + @Test + void getSchemas_whenCatalogArgPercentSign_expectNoResults() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas("%", "publ%"); + + assertFalse(rs.next()); + } + @Test void getSchemas() throws SQLException { DatabaseMetaData dbmd = conn.getMetaData(); - ResultSet rs = dbmd.getSchemas("", "publ%"); + ResultSet rs = dbmd.getSchemas(null, "publ%"); assertTrue(rs.next()); assertEquals("public", rs.getString("TABLE_SCHEM")); - assertNull(rs.getString("TABLE_CATALOG")); + assertNotNull(rs.getString("TABLE_CATALOG")); assertFalse(rs.next()); } + @Test + void getSchemas_whenNullCatalogAndSchemaPattern_expectRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(null, null); + + assertTrue(rs.next()); + } + + @Test + void getSchemas_whenEmptySchemaPattern_expectNoRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(null, ""); + + assertFalse(rs.next()); + } + + @Test + void getSchemas_whenEmptyCatalog_expectNoRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas("", null); + + assertFalse(rs.next()); + } + + @Test + void getSchemas_whenRandomCatalog_expectNoRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(UUID.randomUUID().toString(), null); + + assertFalse(rs.next()); + } + + @Test + void getFunctionsInSchemaForFunctions_whenCatalogArgEmpty_expectNoResults() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", "addfunction")) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", "nofunctions", null)) { + boolean hasFunctions = rs.next(); + assertFalse(hasFunctions, "There should be no functions in the nofunctions schema"); + } + } + @Test void getFunctionsInSchemaForFunctions() throws SQLException { DatabaseMetaData dbmd = conn.getMetaData(); - try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", "")) { + try (ResultSet rs = dbmd.getFunctions(null, "hasfunctions", null)) { List<CatalogObject> list = assertFunctionRSAndReturnList(rs); assertEquals(1, list.size(), "There should be one function in the hasfunctions schema"); assertListContains("getFunctions('', 'hasfunctions', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", "addfunction")) { + try (ResultSet rs = dbmd.getFunctions(null, "hasfunctions", "addfunction")) { List<CatalogObject> list = assertFunctionRSAndReturnList(rs); assertEquals(1, list.size(), "There should be one function in the hasfunctions schema with name addfunction"); assertListContains("getFunctions('', 'hasfunctions', 'addfunction') must contain addfunction", list, "hasfunctions", "addfunction"); } - try (ResultSet rs = dbmd.getFunctions("", "nofunctions", "")) { + try (ResultSet rs = dbmd.getFunctions(null, "nofunctions", null)) { boolean hasFunctions = rs.next(); assertFalse(hasFunctions, "There should be no functions in the nofunctions schema"); } } @Test - void getFunctionsInSchemaForProcedures() throws SQLException { + void getFunctionsInSchemaForProcedures_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Due to the introduction of actual stored procedures in PostgreSQL 11, getFunctions should not return procedures for PostgreSQL versions 11+ // On older installation we do not create the procedures so the below schemas should all be empty DatabaseMetaData dbmd = conn.getMetaData(); @@ -148,12 +219,58 @@ class DatabaseMetaDataTest { } @Test - void getProceduresInSchemaForFunctions() throws SQLException { + void getFunctionsInSchemaForProcedures() throws SQLException { + // Due to the introduction of actual stored procedures in PostgreSQL 11, getFunctions should not return procedures for PostgreSQL versions 11+ + // On older installation we do not create the procedures so the below schemas should all be empty + DatabaseMetaData dbmd = conn.getMetaData(); + + // Search for functions in schema "hasprocedures" + try (ResultSet rs = dbmd.getFunctions(null, "hasprocedures", null)) { + assertFalse(rs.next(), "The hasprocedures schema not return procedures from getFunctions"); + } + // Search for functions in schema "noprocedures" (which should never expect records) + try (ResultSet rs = dbmd.getFunctions(null, "noprocedures", null)) { + assertFalse(rs.next(), "The noprocedures schema should not have functions"); + } + // Search for functions by procedure name "addprocedure" + try (ResultSet rs = dbmd.getFunctions(null, "hasprocedures", "addprocedure")) { + assertFalse(rs.next(), "Should not return procedures from getFunctions by schema + name"); + } + } + + @Test + void getProceduresInSchemaForFunctions_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Due to the introduction of actual stored procedures in PostgreSQL 11, getProcedures should not return functions for PostgreSQL versions 11+ DatabaseMetaData dbmd = conn.getMetaData(); // Search for procedures in schema "hasfunctions" (which should expect a record only for PostgreSQL < 11) try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", null)) { + assertFalse(rs.next()); + } + + // Search for procedures in schema "nofunctions" (which should never expect records) + try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions"); + } + + // Search for procedures by function name "addfunction" within schema "hasfunctions" (which should expect a record for PostgreSQL < 11) + try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", "addfunction")) { + assertFalse(rs.next()); + } + + // Search for procedures by function name "addfunction" within schema "nofunctions" (which should never expect records) + try (ResultSet rs = dbmd.getProcedures("", "nofunctions", "addfunction")) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions + addfunction"); + } + } + + @Test + void getProceduresInSchemaForFunctions() throws SQLException { + // Due to the introduction of actual stored procedures in PostgreSQL 11, getProcedures should not return functions for PostgreSQL versions 11+ + DatabaseMetaData dbmd = conn.getMetaData(); + + // Search for procedures in schema "hasfunctions" (which should expect a record only for PostgreSQL < 11) + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", null)) { if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { assertFalse(rs.next(), "PostgreSQL11+ should not return functions from getProcedures"); } else { @@ -163,12 +280,12 @@ class DatabaseMetaDataTest { } // Search for procedures in schema "nofunctions" (which should never expect records) - try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", null)) { assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions"); } // Search for procedures by function name "addfunction" within schema "hasfunctions" (which should expect a record for PostgreSQL < 11) - try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", "addfunction")) { + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", "addfunction")) { if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { assertFalse(rs.next(), "PostgreSQL11+ should not return functions from getProcedures"); } else { @@ -178,29 +295,28 @@ class DatabaseMetaDataTest { } // Search for procedures by function name "addfunction" within schema "nofunctions" (which should never expect records) - try (ResultSet rs = dbmd.getProcedures("", "nofunctions", "addfunction")) { + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", "addfunction")) { assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions + addfunction"); } } @Test - void getProceduresInSchemaForProcedures() throws SQLException { + void getProceduresInSchemaForProcedures_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Only run this test for PostgreSQL version 11+; assertions for versions prior would be vacuously true as we don't create a procedure in the setup for older versions Assumptions.assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); DatabaseMetaData dbmd = conn.getMetaData(); try (ResultSet rs = dbmd.getProcedures("", "hasprocedures", null)) { - int count = assertProcedureRS(rs); - assertEquals(1, count, "getProcedures() should be non-empty for the hasprocedures schema"); + assertFalse(rs.next()); } try (ResultSet rs = dbmd.getProcedures("", "noprocedures", null)) { - assertFalse(rs.next(), "getProcedures() should be empty for the hasprocedures schema"); + assertFalse(rs.next(), "getProcedures() should be empty for the noprocedures schema"); } try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", null)) { - assertFalse(rs.next(), "getProcedures() should be empty for the nofunctions schema"); + assertFalse(rs.next(), "getProcedures() should be empty for the hasfunctions schema"); } try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { @@ -208,6 +324,51 @@ class DatabaseMetaDataTest { } } + @Test + void getProceduresInSchemaForProcedures() throws SQLException { + // Only run this test for PostgreSQL version 11+; assertions for versions prior would be vacuously true as we don't create a procedure in the setup for older versions + Assumptions.assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); + + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getProcedures(null, "hasprocedures", null)) { + int count = assertProcedureRS(rs); + assertEquals(1, count, "getProcedures() should be non-empty for the hasprocedures schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "noprocedures", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the noprocedures schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the hasfunctions schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the nofunctions schema"); + } + } + + @Test + void getFunctionsWithEmptyPatterns() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + try (ResultSet rs = dbmd.getFunctions("", "", "")) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", null, null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions(null, "", null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions(null, null, "")) { + assertFalse(rs.next()); + } + } + @Test void getFunctionsWithBlankPatterns() throws SQLException { int minFuncCount = 1000; @@ -215,38 +376,71 @@ class DatabaseMetaDataTest { final int totalCount; try (ResultSet rs = dbmd.getFunctions("", "", "")) { + assertFalse(rs.next()); + } + + // Should not be same as blank pattern + try (ResultSet rs = dbmd.getFunctions(null, null, null)) { List<CatalogObject> list = assertFunctionRSAndReturnList(rs); totalCount = list.size(); // Rest of this test will validate against this value assertThat(totalCount > minFuncCount, is(true)); assertListContains("getFunctions('', '', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - // Should be same as blank pattern + // Catalog parameter has effect on our getFunctions filtering + try (ResultSet rs = dbmd.getFunctions("ANYTHING_WILL_WORK", null, null)) { + assertFalse(rs.next()); + } + + // Filter by schema + try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", null)) { + assertFalse(rs.next()); + } + + // Filter by schema and function name + try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", "abs")) { + assertFalse(rs.next()); + } + + // Filter by function name only + try (ResultSet rs = dbmd.getFunctions("", "", "abs")) { + assertFalse(rs.next()); + } + } + + @Test + void getFunctionsWithNullPatterns() throws SQLException { + int minFuncCount = 1000; + DatabaseMetaData dbmd = conn.getMetaData(); + + final int totalCount; + try (ResultSet rs = dbmd.getFunctions(null, null, null)) { - int count = assertGetFunctionRS(rs); - assertThat(count, is(totalCount)); + List<CatalogObject> list = assertFunctionRSAndReturnList(rs); + totalCount = list.size(); // Rest of this test will validate against this value + assertThat(totalCount > minFuncCount, is(true)); + assertListContains("getFunctions('', '', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - // Catalog parameter has no affect on our getFunctions filtering - try (ResultSet rs = dbmd.getFunctions("ANYTHING_WILL_WORK", null, null)) { - int count = assertGetFunctionRS(rs); - assertThat(count, is(totalCount)); + // Catalog parameter has effect on our getFunctions filtering + try (ResultSet rs = dbmd.getFunctions(UUID.randomUUID().toString(), null, null)) { + assertFalse(rs.next()); } // Filter by schema - try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", null)) { + try (ResultSet rs = dbmd.getFunctions(null, "pg_catalog", null)) { int count = assertGetFunctionRS(rs); assertThat(count > minFuncCount, is(true)); } // Filter by schema and function name - try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", "abs")) { + try (ResultSet rs = dbmd.getFunctions(null, "pg_catalog", "abs")) { int count = assertGetFunctionRS(rs); assertThat(count >= 1, is(true)); } // Filter by function name only - try (ResultSet rs = dbmd.getFunctions("", "", "abs")) { + try (ResultSet rs = dbmd.getFunctions(null, null, "abs")) { int count = assertGetFunctionRS(rs); assertThat(count >= 1, is(true)); } @@ -357,7 +551,7 @@ class DatabaseMetaDataTest { private List<CatalogObject> assertProcedureRSAndReturnList(ResultSet rs) throws SQLException { // There should be at least one row assertThat(rs.next(), is(true)); - assertThat(rs.getString("PROCEDURE_CAT"), nullValue()); + assertThat(rs.getString("PROCEDURE_CAT"), is(System.getProperty("database"))); assertThat(rs.getString("PROCEDURE_SCHEM"), notNullValue()); assertThat(rs.getString("PROCEDURE_NAME"), notNullValue()); assertThat(rs.getShort("PROCEDURE_TYPE") >= 0, is(true)); @@ -397,6 +591,55 @@ class DatabaseMetaDataTest { assertTrue(found, message + "; schema=" + schema + " name=" + name); } + @Test + void getFunctionsWithSpecificTypes_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { + // These function creation are borrow from jdbc2/DatabaseMetaDataTest + // We modify to ensure new function created are returned by getFunctions() + + DatabaseMetaData dbmd = conn.getMetaData(); + if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_4)) { + Statement stmt = conn.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); + + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); + rs = dbmd.getFunctions("", "", "getfunc_f3"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); + + // RETURNS TABLE requires PostgreSQL 8.4+ + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); + + rs = dbmd.getFunctions("", "", "getfunc_f5"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f5()"); + } else { + // For PG 8.3 or 8.2 it will resulted in unknown function type + Statement stmt = conn.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); + + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); + rs = dbmd.getFunctions("", "", "getfunc_f3"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); + } + } + @Test void getFunctionsWithSpecificTypes() throws SQLException { // These function creation are borrow from jdbc2/DatabaseMetaDataTest @@ -407,7 +650,7 @@ class DatabaseMetaDataTest { Statement stmt = conn.createStatement(); stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); - ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + ResultSet rs = dbmd.getFunctions(null, null, "getfunc_f1"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1")); assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionNoTable)); @@ -417,7 +660,7 @@ class DatabaseMetaDataTest { stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); - rs = dbmd.getFunctions("", "", "getfunc_f3"); + rs = dbmd.getFunctions(null, null, "getfunc_f3"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3")); assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionNoTable)); @@ -429,7 +672,7 @@ class DatabaseMetaDataTest { stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); - rs = dbmd.getFunctions("", "", "getfunc_f5"); + rs = dbmd.getFunctions(null, null, "getfunc_f5"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f5")); assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionReturnsTable)); @@ -441,7 +684,7 @@ class DatabaseMetaDataTest { Statement stmt = conn.createStatement(); stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); - ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + ResultSet rs = dbmd.getFunctions(null, null, "getfunc_f1"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1")); assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionResultUnknown)); @@ -451,7 +694,7 @@ class DatabaseMetaDataTest { stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); - rs = dbmd.getFunctions("", "", "getfunc_f3"); + rs = dbmd.getFunctions(null, null, "getfunc_f3"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3")); assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionResultUnknown)); diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java index 9878b910..3a98eb14 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java @@ -54,10 +54,18 @@ class DatabaseMetaDataTest { } @Test - void getColumnsForNullScale() throws Exception { + void getColumnsForNullScale_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "decimaltest", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForNullScale() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "decimaltest", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(0, rs.getInt("DECIMAL_DIGITS")); @@ -72,10 +80,18 @@ class DatabaseMetaDataTest { } @Test - void getCorrectSQLTypeForOffPathTypes() throws Exception { + void getCorrectSQLTypeForOffPathTypes_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "off_path_table", "%"); + assertFalse(rs.next()); + } + + @Test + void getCorrectSQLTypeForOffPathTypes() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "off_path_table", "%"); assertTrue(rs.next()); assertEquals("var", rs.getString("COLUMN_NAME")); assertEquals("\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME"), "Detects correct off-path type name"); @@ -85,11 +101,20 @@ class DatabaseMetaDataTest { } @Test - void getCorrectSQLTypeForShadowedTypes() throws Exception { + void getCorrectSQLTypeForShadowedTypes_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "on_path_table", "%"); + assertFalse(rs.next()); + } + + @Test + void getCorrectSQLTypeForShadowedTypes() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "on_path_table", "%"); + assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals("\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME"), "Correctly maps types from other schemas"); [parent: 7f4c9f8facb4]