From 57040f78f76afe86aa5345f7d7c3e18e7f2d40ef Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Thu, 6 Sep 2001 03:20:30 +0000 Subject: [PATCH] Attached is a patch for JDBC's getColumn() function that was broken / flawed in the following ways: 1. Only returned columns that had a default value defined, rather than all columns in a table 2. Used 2 * N + 1 queries to find out attributes, comments and typenames for N columns. By using some outer join syntax it is possible to retrieve all necessary information in just one SQL statement. This means this version is only suitable for PostgreSQL >= 7.1. Don't know whether that's a problem. I've tested this function with current sources and 7.1.3 and patched both jdbc1 and jdbc2. I haven't compiled nor tested the jdbc1 version though, as I have no JDK 1.1 available. Note the discussion in http://fts.postgresql.org/db/mw/msg.html?mid=1029626 regarding differences in obtaining comments on database object in 7.1 and 7.2. I was unable to use the following syntax (or similar ones): select ..., description from ... left outer join col_description(a.attrelid, a.attnum) description order by c.relname, a.attnum; (the error was parse error at or near '(') so I had to paste the actual code for the col_description function into the left outer join. Maybe someone who is more knowledgable about outer joins might provide me with a better SQL statement. Jeroen van Vianen --- .../postgresql/jdbc1/DatabaseMetaData.java | 205 ++++++++-------- .../postgresql/jdbc2/DatabaseMetaData.java | 218 ++++++++++-------- 2 files changed, 230 insertions(+), 193 deletions(-) diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java index e1187b3094..2d9ab35a93 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java @@ -13,6 +13,8 @@ import org.postgresql.util.PSQLException; /** * This class provides information about the database as a whole. * + * $Id: DatabaseMetaData.java,v 1.30 2001/09/06 03:20:30 momjian Exp $ + * *

Many of the methods here return lists of information in ResultSets. You * can use the normal ResultSet methods such as getString and getInt to * retrieve the data from these ResultSets. If a given form of metadata is @@ -1893,21 +1895,19 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData */ public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { - // the field descriptors for the new ResultSet - Field f[] = new Field[18]; - java.sql.ResultSet r; // ResultSet for the SQL query that we need to do Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field(connection, "TABLE_CAT", iVarcharOid, 32); - f[1] = new Field(connection, "TABLE_SCHEM", iVarcharOid, 32); - f[2] = new Field(connection, "TABLE_NAME", iVarcharOid, 32); - f[3] = new Field(connection, "COLUMN_NAME", iVarcharOid, 32); - f[4] = new Field(connection, "DATA_TYPE", iInt2Oid, 2); - f[5] = new Field(connection, "TYPE_NAME", iVarcharOid, 32); - f[6] = new Field(connection, "COLUMN_SIZE", iInt4Oid, 4); - f[7] = new Field(connection, "BUFFER_LENGTH", iVarcharOid, 32); - f[8] = new Field(connection, "DECIMAL_DIGITS", iInt4Oid, 4); - f[9] = new Field(connection, "NUM_PREC_RADIX", iInt4Oid, 4); + Field f[] = new Field[18]; // The field descriptors for the new ResultSet + + f[ 0] = new Field(connection, "TABLE_CAT", iVarcharOid, 32); + f[ 1] = new Field(connection, "TABLE_SCHEM", iVarcharOid, 32); + f[ 2] = new Field(connection, "TABLE_NAME", iVarcharOid, 32); + f[ 3] = new Field(connection, "COLUMN_NAME", iVarcharOid, 32); + f[ 4] = new Field(connection, "DATA_TYPE", iInt2Oid, 2); + f[ 5] = new Field(connection, "TYPE_NAME", iVarcharOid, 32); + f[ 6] = new Field(connection, "COLUMN_SIZE", iInt4Oid, 4); + f[ 7] = new Field(connection, "BUFFER_LENGTH", iVarcharOid, 32); + f[ 8] = new Field(connection, "DECIMAL_DIGITS", iInt4Oid, 4); + f[ 9] = new Field(connection, "NUM_PREC_RADIX", iInt4Oid, 4); f[10] = new Field(connection, "NULLABLE", iInt4Oid, 4); f[11] = new Field(connection, "REMARKS", iVarcharOid, 32); f[12] = new Field(connection, "COLUMN_DEF", iVarcharOid, 32); @@ -1917,91 +1917,110 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData f[16] = new Field(connection, "ORDINAL_POSITION", iInt4Oid,4); f[17] = new Field(connection, "IS_NULLABLE", iVarcharOid, 32); - // Added by Stefan Andreasen - // If the pattern are null then set them to % - if (tableNamePattern == null) tableNamePattern="%"; - if (columnNamePattern == null) columnNamePattern="%"; - - // Now form the query - String query = - "select " + - (connection.haveMinimumServerVersion("7.2") ? "a.attrelid" : "a.oid") + - ",c.relname,a.attname,a.atttypid," + - "a.attnum,a.attnotnull,a.attlen,a.atttypmod,d.adsrc from pg_class c," + - "pg_attribute a,pg_attrdef d where a.attrelid=c.oid and " + - "c.relname like '"+tableNamePattern.toLowerCase()+"' and " + - "a.attname like '"+columnNamePattern.toLowerCase()+"' and " + - "a.attnum>0 and c.oid=d.adrelid and d.adnum=a.attnum " + - "order by c.relname,a.attnum"; - - r = connection.ExecSQL(query); - - while(r.next()) { - byte[][] tuple = new byte[18][0]; - - // Fetch the description for the table (if any) - String getDescriptionStatement = - connection.haveMinimumServerVersion("7.2") ? - "select col_description(" + r.getInt(1) + "," + r.getInt(5) + ")" : - "select description from pg_description where objoid=" + r.getInt(1); - - java.sql.ResultSet dr = connection.ExecSQL(getDescriptionStatement); - - if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { - dr.next(); - tuple[11] = dr.getBytes(1); - } else - tuple[11] = null; - dr.close(); - - tuple[0] = "".getBytes(); // Catalog name - tuple[1] = "".getBytes(); // Schema name - tuple[2] = r.getBytes(2); // Table name - tuple[3] = r.getBytes(3); // Column name - - dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4)); - dr.next(); - String typname=dr.getString(1); - dr.close(); - tuple[4] = Integer.toString(connection.getSQLType(typname)).getBytes(); // Data type - tuple[5] = typname.getBytes(); // Type name - - // Column size - // Looking at the psql source, - // I think the length of a varchar as specified when the table was created - // should be extracted from atttypmod which contains this length + sizeof(int32) - if (typname.equals("bpchar") || typname.equals("varchar")) { - int atttypmod = r.getInt(8); - tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); - } else - tuple[6] = r.getBytes(7); - - tuple[7] = null; // Buffer length - - tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? - tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal - - // tuple[10] is below - // tuple[11] is above - - tuple[12] = r.getBytes(9); // column default - - tuple[13] = null; // sql data type (unused) - tuple[14] = null; // sql datetime sub (unused) + StringBuffer sql = new StringBuffer(512); + + sql.append("select " + + (connection.haveMinimumServerVersion("7.2") ? "a.attrelid, " : "a.oid, ") + + " c.relname, " + + " a.attname, " + + " a.atttypid, " + + " a.attnum, " + + " a.attnotnull, " + + " a.attlen, " + + " a.atttypmod, " + + " d.adsrc, " + + " t.typname, " + + " e.description " + + "from" + + " (" + + " (pg_class c inner join pg_attribute a on" + + " (" + + " a.attrelid=c.oid"); + + if ((tableNamePattern != null) && ! tableNamePattern.equals("%")) { + sql.append(" and c.relname like \'" + tableNamePattern + "\'"); + } - tuple[15] = tuple[6]; // char octet length + if ((columnNamePattern != null) && ! columnNamePattern.equals("%")) { + sql.append(" and a.attname like \'" + columnNamePattern + "\'"); + } - tuple[16] = r.getBytes(5); // ordinal position + sql.append( + " and a.attnum > 0" + + " )" + + " ) inner join pg_type t on" + + " (" + + " t.oid = a.atttypid" + + " )" + + " )" + + " left outer join pg_attrdef d on" + + " (" + + " c.oid = d.adrelid" + + " and a.attnum = d.adnum" + + " )" + + " left outer join pg_description e on" + + " (" + + " e.objoid = a.attrelid"); + + if (connection.haveMinimumServerVersion("7.2")) { + sql.append( + " and e.objsubid = a.attnum" + + " and e.classoid = (select oid from pg_class where relname = \'pg_class\')"); + } - String nullFlag = r.getString(6); - tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable - tuple[17] = (nullFlag.equals("f")?"YES":"NO").getBytes(); // is nullable + sql.append( + " ) " + + "order by" + + " c.relname, a.attnum"); + + System.out.println(sql); + + java.sql.ResultSet r = connection.ExecSQL(sql.toString()); + while (r.next()) { + byte[][] tuple = new byte[18][0]; + + String nullFlag = r.getString(6); + String typname = r.getString(10); + + tuple[0] = "".getBytes(); // Catalog name + tuple[1] = "".getBytes(); // Schema name + tuple[2] = r.getBytes(2); // Table name + tuple[3] = r.getBytes(3); // Column name + tuple[4] = Integer.toString(connection.getSQLType(typname)).getBytes(); // Data type + tuple[5] = typname.getBytes(); // Type name + + // Column size + // Looking at the psql source, + // I think the length of a varchar as specified when the table was created + // should be extracted from atttypmod which contains this length + sizeof(int32) + if (typname.equals("bpchar") || typname.equals("varchar")) { + int atttypmod = r.getInt(8); + tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); + } else { + tuple[6] = r.getBytes(7); + } + + tuple[7] = null; // Buffer length + tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? + tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal + tuple[10] = Integer.toString(nullFlag.equals("f") ? + java.sql.DatabaseMetaData.columnNullable : + java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable + tuple[11] = r.getBytes(11); // Description (if any) + tuple[12] = r.getBytes(9); // Column default + tuple[13] = null; // sql data type (unused) + tuple[14] = null; // sql datetime sub (unused) + tuple[15] = tuple[6]; // char octet length + tuple[16] = r.getBytes(5); // ordinal position + tuple[17] = (nullFlag.equals("f") ? "YES" : "NO").getBytes(); // Is nullable + + v.addElement(tuple); + } + r.close(); - v.addElement(tuple); - } - r.close(); return new ResultSet(connection, f, v, "OK", 1); } + /** * Get a description of the access rights for a table's columns. * diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java index 6e4c01c333..b41f446f97 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java @@ -13,6 +13,8 @@ import org.postgresql.util.PSQLException; /** * This class provides information about the database as a whole. * + * $Id: DatabaseMetaData.java,v 1.34 2001/09/06 03:20:30 momjian Exp $ + * *

Many of the methods here return lists of information in ResultSets. You * can use the normal ResultSet methods such as getString and getInt to * retrieve the data from these ResultSets. If a given form of metadata is @@ -677,9 +679,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData /** * Does this driver support the ANSI-92 entry level SQL grammar? - * All JDBC Compliant drivers must return true. We currently - * report false until 'schema' support is added. Then this - * should be changed to return true, since we will be mostly + * All JDBC Compliant drivers must return true. We currently + * report false until 'schema' support is added. Then this + * should be changed to return true, since we will be mostly * compliant (probably more compliant than many other databases) * And since this is a requirement for all JDBC drivers we * need to get to the point where we can return true. @@ -1893,21 +1895,19 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData */ public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { - // the field descriptors for the new ResultSet - Field f[] = new Field[18]; - java.sql.ResultSet r; // ResultSet for the SQL query that we need to do Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field(connection, "TABLE_CAT", iVarcharOid, 32); - f[1] = new Field(connection, "TABLE_SCHEM", iVarcharOid, 32); - f[2] = new Field(connection, "TABLE_NAME", iVarcharOid, 32); - f[3] = new Field(connection, "COLUMN_NAME", iVarcharOid, 32); - f[4] = new Field(connection, "DATA_TYPE", iInt2Oid, 2); - f[5] = new Field(connection, "TYPE_NAME", iVarcharOid, 32); - f[6] = new Field(connection, "COLUMN_SIZE", iInt4Oid, 4); - f[7] = new Field(connection, "BUFFER_LENGTH", iVarcharOid, 32); - f[8] = new Field(connection, "DECIMAL_DIGITS", iInt4Oid, 4); - f[9] = new Field(connection, "NUM_PREC_RADIX", iInt4Oid, 4); + Field f[] = new Field[18]; // The field descriptors for the new ResultSet + + f[ 0] = new Field(connection, "TABLE_CAT", iVarcharOid, 32); + f[ 1] = new Field(connection, "TABLE_SCHEM", iVarcharOid, 32); + f[ 2] = new Field(connection, "TABLE_NAME", iVarcharOid, 32); + f[ 3] = new Field(connection, "COLUMN_NAME", iVarcharOid, 32); + f[ 4] = new Field(connection, "DATA_TYPE", iInt2Oid, 2); + f[ 5] = new Field(connection, "TYPE_NAME", iVarcharOid, 32); + f[ 6] = new Field(connection, "COLUMN_SIZE", iInt4Oid, 4); + f[ 7] = new Field(connection, "BUFFER_LENGTH", iVarcharOid, 32); + f[ 8] = new Field(connection, "DECIMAL_DIGITS", iInt4Oid, 4); + f[ 9] = new Field(connection, "NUM_PREC_RADIX", iInt4Oid, 4); f[10] = new Field(connection, "NULLABLE", iInt4Oid, 4); f[11] = new Field(connection, "REMARKS", iVarcharOid, 32); f[12] = new Field(connection, "COLUMN_DEF", iVarcharOid, 32); @@ -1917,89 +1917,107 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData f[16] = new Field(connection, "ORDINAL_POSITION", iInt4Oid,4); f[17] = new Field(connection, "IS_NULLABLE", iVarcharOid, 32); - // Added by Stefan Andreasen - // If the pattern are null then set them to % - if (tableNamePattern == null) tableNamePattern="%"; - if (columnNamePattern == null) columnNamePattern="%"; - - // Now form the query - String query = - "select " + - (connection.haveMinimumServerVersion("7.2") ? "a.attrelid" : "a.oid") + - ",c.relname,a.attname,a.atttypid," + - "a.attnum,a.attnotnull,a.attlen,a.atttypmod,d.adsrc from pg_class c," + - "pg_attribute a,pg_attrdef d where a.attrelid=c.oid and " + - "c.relname like '"+tableNamePattern.toLowerCase()+"' and " + - "a.attname like '"+columnNamePattern.toLowerCase()+"' and " + - "a.attnum>0 and c.oid=d.adrelid and d.adnum=a.attnum " + - "order by c.relname,a.attnum"; - - r = connection.ExecSQL(query); - - while(r.next()) { - byte[][] tuple = new byte[18][0]; - - // Fetch the description for the table (if any) - String getDescriptionStatement = - connection.haveMinimumServerVersion("7.2") ? - "select col_description(" + r.getInt(1) + "," + r.getInt(5) + ")" : - "select description from pg_description where objoid=" + r.getInt(1); - - java.sql.ResultSet dr = connection.ExecSQL(getDescriptionStatement); - - if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { - dr.next(); - tuple[11] = dr.getBytes(1); - } else - tuple[11] = null; - dr.close(); - - tuple[0] = "".getBytes(); // Catalog name - tuple[1] = "".getBytes(); // Schema name - tuple[2] = r.getBytes(2); // Table name - tuple[3] = r.getBytes(3); // Column name - - dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4)); - dr.next(); - String typname=dr.getString(1); - dr.close(); - tuple[4] = Integer.toString(connection.getSQLType(typname)).getBytes(); // Data type - tuple[5] = typname.getBytes(); // Type name - - // Column size - // Looking at the psql source, - // I think the length of a varchar as specified when the table was created - // should be extracted from atttypmod which contains this length + sizeof(int32) - if (typname.equals("bpchar") || typname.equals("varchar")) { - int atttypmod = r.getInt(8); - tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); - } else - tuple[6] = r.getBytes(7); - - tuple[7] = null; // Buffer length - - tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? - tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal - - // tuple[10] is below - // tuple[11] is above - - tuple[12] = r.getBytes(9); // column default - - tuple[13] = null; // sql data type (unused) - tuple[14] = null; // sql datetime sub (unused) + StringBuffer sql = new StringBuffer(512); + + sql.append("select " + + (connection.haveMinimumServerVersion("7.2") ? "a.attrelid, " : "a.oid, ") + + " c.relname, " + + " a.attname, " + + " a.atttypid, " + + " a.attnum, " + + " a.attnotnull, " + + " a.attlen, " + + " a.atttypmod, " + + " d.adsrc, " + + " t.typname, " + + " e.description " + + "from" + + " (" + + " (pg_class c inner join pg_attribute a on" + + " (" + + " a.attrelid=c.oid"); + + if ((tableNamePattern != null) && ! tableNamePattern.equals("%")) { + sql.append(" and c.relname like \'" + tableNamePattern + "\'"); + } - tuple[15] = tuple[6]; // char octet length + if ((columnNamePattern != null) && ! columnNamePattern.equals("%")) { + sql.append(" and a.attname like \'" + columnNamePattern + "\'"); + } - tuple[16] = r.getBytes(5); // ordinal position + sql.append( + " and a.attnum > 0" + + " )" + + " ) inner join pg_type t on" + + " (" + + " t.oid = a.atttypid" + + " )" + + " )" + + " left outer join pg_attrdef d on" + + " (" + + " c.oid = d.adrelid" + + " and a.attnum = d.adnum" + + " )" + + " left outer join pg_description e on" + + " (" + + " e.objoid = a.attrelid"); + + if (connection.haveMinimumServerVersion("7.2")) { + sql.append( + " and e.objsubid = a.attnum" + + " and e.classoid = (select oid from pg_class where relname = \'pg_class\')"); + } - String nullFlag = r.getString(6); - tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable - tuple[17] = (nullFlag.equals("f")?"YES":"NO").getBytes(); // is nullable + sql.append( + " ) " + + "order by" + + " c.relname, a.attnum"); + + System.out.println(sql); + + java.sql.ResultSet r = connection.ExecSQL(sql.toString()); + while (r.next()) { + byte[][] tuple = new byte[18][0]; + + String nullFlag = r.getString(6); + String typname = r.getString(10); + + tuple[0] = "".getBytes(); // Catalog name + tuple[1] = "".getBytes(); // Schema name + tuple[2] = r.getBytes(2); // Table name + tuple[3] = r.getBytes(3); // Column name + tuple[4] = Integer.toString(connection.getSQLType(typname)).getBytes(); // Data type + tuple[5] = typname.getBytes(); // Type name + + // Column size + // Looking at the psql source, + // I think the length of a varchar as specified when the table was created + // should be extracted from atttypmod which contains this length + sizeof(int32) + if (typname.equals("bpchar") || typname.equals("varchar")) { + int atttypmod = r.getInt(8); + tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); + } else { + tuple[6] = r.getBytes(7); + } + + tuple[7] = null; // Buffer length + tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? + tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal + tuple[10] = Integer.toString(nullFlag.equals("f") ? + java.sql.DatabaseMetaData.columnNullable : + java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable + tuple[11] = r.getBytes(11); // Description (if any) + tuple[12] = r.getBytes(9); // Column default + tuple[13] = null; // sql data type (unused) + tuple[14] = null; // sql datetime sub (unused) + tuple[15] = tuple[6]; // char octet length + tuple[16] = r.getBytes(5); // ordinal position + tuple[17] = (nullFlag.equals("f") ? "YES" : "NO").getBytes(); // Is nullable + + v.addElement(tuple); + } + r.close(); - v.addElement(tuple); - } - r.close(); return new ResultSet(connection, f, v, "OK", 1); } @@ -2387,9 +2405,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData "from pg_class as c, pg_trigger as t "+ "where c.relname like '"+table+"' and c.relfilenode=t.tgrelid"); Vector tuples=new Vector(); - + while (rs.next()) { - tuples.addAll(importLoop(rs)); + tuples.addAll(importLoop(rs)); } rsret=new ResultSet(connection, f, tuples, "OK", 1); @@ -2707,7 +2725,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData " AND (i.oid = x.indexrelid) " + " AND (i.relam = a.oid)) " + "ORDER BY x.indisunique DESC, " + - " x.indisclustered, a.amname, i.relname"); + " x.indisclustered, a.amname, i.relname"); while (r.next()) { // indkey is an array of column ordinals (integers). In the JDBC // interface, this has to be separated out into a separate @@ -2722,7 +2740,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData } for (int i = 0; i < columnOrdinals.length; i++) { byte [] [] tuple = new byte [13] []; - tuple[0] = "".getBytes(); + tuple[0] = "".getBytes(); tuple[1] = "".getBytes(); tuple[2] = r.getBytes(1); tuple[3] = r.getBoolean(2) ? "f".getBytes() : "t".getBytes(); -- 2.40.0