/* * Copyright (c) 2005, 2006 TADA AB - Taby Sweden * Copyright (c) 2005, 2010, 2011 PostgreSQL Global Development Group * * Distributed under the terms shown in the file COPYRIGHT * found in the root folder of this project or at * http://wiki.tada.se/index.php?title=PLJava_License */ package org.postgresql.pljava.jdbc; /** * @author Filip Hrbek */ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.RowIdLifetime; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import org.postgresql.pljava.internal.AclId; import org.postgresql.pljava.internal.Backend; import org.postgresql.pljava.internal.Oid; public class SPIDatabaseMetaData implements DatabaseMetaData { public SPIDatabaseMetaData(SPIConnection conn) { m_connection = conn; } private static final String KEYWORDS = "abort,acl,add,aggregate,append,archive," + "arch_store,backward,binary,boolean,change,cluster," + "copy,database,delimiter,delimiters,do,extend," + "explain,forward,heavy,index,inherits,isnull," + "light,listen,load,merge,nothing,notify," + "notnull,oids,purge,rename,replace,retrieve," + "returns,rule,recipe,setof,stdin,stdout,store," + "vacuum,verbose,version"; private final SPIConnection m_connection; // The connection association private static final int VARHDRSZ = 4; // length for int4 private int NAMEDATALEN = 0; // length for name datatype private int INDEX_MAX_KEYS = 0; // maximum number of keys in an index. protected int getMaxIndexKeys() throws SQLException { if(INDEX_MAX_KEYS == 0) INDEX_MAX_KEYS = Integer.parseInt(Backend.getConfigOption("max_index_keys")); return INDEX_MAX_KEYS; } protected int getMaxNameLength() throws SQLException { if(NAMEDATALEN == 0) { String sql = "SELECT t.typlen FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n" + " WHERE t.typnamespace=n.oid" + " AND t.typname='name'" + " AND n.nspname='pg_catalog'"; ResultSet rs = m_connection.createStatement().executeQuery(sql); if(!rs.next()){ throw new SQLException( "Unable to find name datatype in the system catalogs."); } NAMEDATALEN = rs.getInt("typlen"); rs.close(); } return NAMEDATALEN - 1; } /* * Can all the procedures returned by getProcedures be called by the current * user? @return true if so @exception SQLException if a database access * error occurs */ public boolean allProceduresAreCallable() throws SQLException { return true; // For now... } /* * Can all the tables returned by getTable be SELECTed by the current user? * @return true if so @exception SQLException if a database access error * occurs */ public boolean allTablesAreSelectable() throws SQLException { return true; // For now... } /* * What is the URL for this database? @return the url or null if it cannott * be generated @exception SQLException if a database access error occurs */ public String getURL() throws SQLException { return "jdbc:default:connection"; } /* * What is our user name as known to the database? @return our database user * name @exception SQLException if a database access error occurs */ public String getUserName() throws SQLException { return AclId.getUser().getName(); } /* * Is the database in read-only mode? @return true if so @exception * SQLException if a database access error occurs */ public boolean isReadOnly() throws SQLException { return m_connection.isReadOnly(); } /* * Are NULL values sorted high? @return true if so @exception SQLException * if a database access error occurs */ public boolean nullsAreSortedHigh() throws SQLException { return true; } /* * Are NULL values sorted low? @return true if so @exception SQLException if * a database access error occurs */ public boolean nullsAreSortedLow() throws SQLException { return false; } /* * Are NULL values sorted at the start regardless of sort order? @return * true if so @exception SQLException if a database access error occurs */ public boolean nullsAreSortedAtStart() throws SQLException { return false; } /* * Are NULL values sorted at the end regardless of sort order? @return true * if so @exception SQLException if a database access error occurs */ public boolean nullsAreSortedAtEnd() throws SQLException { return false; } /* * What is the name of this database product - we hope that it is * PostgreSQL, so we return that explicitly. @return the database product * name @exception SQLException if a database access error occurs */ public String getDatabaseProductName() throws SQLException { return "PostgreSQL"; } /* * What is the version of this database product. @return the database * version @exception SQLException if a database access error occurs */ public String getDatabaseProductVersion() throws SQLException { int[] ver = m_connection.getVersionNumber(); return ver[0] + "." + ver[1] + "." + ver[2]; } /* * What is the name of this JDBC driver? If we don't know this we are doing * something wrong! @return the JDBC driver name @exception SQLException * why? */ public String getDriverName() throws SQLException { return "PostgreSQL pljava SPI Driver"; } /* * What is the version string of this JDBC driver? Again, this is static. * @return the JDBC driver name. @exception SQLException why? */ public String getDriverVersion() throws SQLException { SPIDriver d = new SPIDriver(); return d.getMajorVersion() + "." + d.getMinorVersion(); } /* * What is this JDBC driver's major version number? @return the JDBC driver * major version */ public int getDriverMajorVersion() { return new SPIDriver().getMajorVersion(); } /* * What is this JDBC driver's minor version number? @return the JDBC driver * minor version */ public int getDriverMinorVersion() { return new SPIDriver().getMinorVersion(); } /* * Does the database store tables in a local file? No - it stores them in a * file on the server. @return true if so @exception SQLException if a * database access error occurs */ public boolean usesLocalFiles() throws SQLException { return false; } /* * Does the database use a file for each table? Well, not really, since it * doesnt use local files. @return true if so @exception SQLException if a * database access error occurs */ public boolean usesLocalFilePerTable() throws SQLException { return false; } /* * Does the database treat mixed case unquoted SQL identifiers as case * sensitive and as a result store them in mixed case? A JDBC-Compliant * driver will always return false. @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsMixedCaseIdentifiers() throws SQLException { return false; } /* * Does the database treat mixed case unquoted SQL identifiers as case * insensitive and store them in upper case? @return true if so */ public boolean storesUpperCaseIdentifiers() throws SQLException { return false; } /* * Does the database treat mixed case unquoted SQL identifiers as case * insensitive and store them in lower case? @return true if so */ public boolean storesLowerCaseIdentifiers() throws SQLException { return true; } /* * Does the database treat mixed case unquoted SQL identifiers as case * insensitive and store them in mixed case? @return true if so */ public boolean storesMixedCaseIdentifiers() throws SQLException { return false; } /* * Does the database treat mixed case quoted SQL identifiers as case * sensitive and as a result store them in mixed case? A JDBC compliant * driver will always return true. @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { return true; } /* * Does the database treat mixed case quoted SQL identifiers as case * insensitive and store them in upper case? @return true if so */ public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { return false; } /* * Does the database treat mixed case quoted SQL identifiers as case * insensitive and store them in lower case? @return true if so */ public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { return false; } /* * Does the database treat mixed case quoted SQL identifiers as case * insensitive and store them in mixed case? @return true if so */ public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { return false; } /* * What is the string used to quote SQL identifiers? This returns a space if * identifier quoting isn't supported. A JDBC Compliant driver will always * use a double quote character. @return the quoting string @exception * SQLException if a database access error occurs */ public String getIdentifierQuoteString() throws SQLException { return "\""; } /* * Get a comma separated list of all a database's SQL keywords that are NOT * also SQL92 keywords. <p>Within PostgreSQL, the keywords are found in * src/backend/parser/keywords.c <p>For SQL Keywords, I took the list * provided at <a * href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt</a> which is for * SQL3, not SQL-92, but it is close enough for this purpose. @return a * comma separated list of keywords we use @exception SQLException if a * database access error occurs */ public String getSQLKeywords() throws SQLException { return KEYWORDS; } /** * get supported escaped numeric functions * * @return a comma separated list of function names */ public String getNumericFunctions() throws SQLException { return BuiltinFunctions.ABS + ',' + BuiltinFunctions.ACOS + ',' + BuiltinFunctions.ASIN + ',' + BuiltinFunctions.ATAN + ',' + BuiltinFunctions.ATAN2 + ',' + BuiltinFunctions.CEILING + ',' + BuiltinFunctions.COS + ',' + BuiltinFunctions.COT + ',' + BuiltinFunctions.DEGREES + ',' + BuiltinFunctions.EXP + ',' + BuiltinFunctions.FLOOR + ',' + BuiltinFunctions.LOG + ',' + BuiltinFunctions.LOG10 + ',' + BuiltinFunctions.MOD + ',' + BuiltinFunctions.PI + ',' + BuiltinFunctions.POWER + ',' + BuiltinFunctions.RADIANS + ',' + BuiltinFunctions.RAND + ',' + BuiltinFunctions.ROUND + ',' + BuiltinFunctions.SIGN + ',' + BuiltinFunctions.SIN + ',' + BuiltinFunctions.SQRT + ',' + BuiltinFunctions.TAN + ',' + BuiltinFunctions.TRUNCATE; } public String getStringFunctions() throws SQLException { String funcs = BuiltinFunctions.ASCII + ',' + BuiltinFunctions.CHAR + ',' + BuiltinFunctions.CONCAT + ',' + BuiltinFunctions.LCASE + ',' + BuiltinFunctions.LEFT + ',' + BuiltinFunctions.LENGTH + ',' + BuiltinFunctions.LTRIM + ',' + BuiltinFunctions.REPEAT + ',' + BuiltinFunctions.RTRIM + ',' + BuiltinFunctions.SPACE + ',' + BuiltinFunctions.SUBSTRING + ',' + BuiltinFunctions.UCASE + ',' + BuiltinFunctions.REPLACE; // Currently these don't work correctly with parameterized // arguments, so leave them out. They reorder the arguments // when rewriting the query, but no translation layer is provided, // so a setObject(N, obj) will not go to the correct parameter. // ','+BuiltinFunctions.INSERT+','+BuiltinFunctions.LOCATE+ // ','+BuiltinFunctions.RIGHT+ return funcs; } public String getSystemFunctions() throws SQLException { return BuiltinFunctions.DATABASE + ',' + BuiltinFunctions.IFNULL + ',' + BuiltinFunctions.USER; } public String getTimeDateFunctions() throws SQLException { return BuiltinFunctions.CURDATE + ',' + BuiltinFunctions.CURTIME + ',' + BuiltinFunctions.DAYNAME + ',' + BuiltinFunctions.DAYOFMONTH + ',' + BuiltinFunctions.DAYOFWEEK + ',' + BuiltinFunctions.DAYOFYEAR + ',' + BuiltinFunctions.HOUR + ',' + BuiltinFunctions.MINUTE + ',' + BuiltinFunctions.MONTH + ',' + BuiltinFunctions.MONTHNAME + ',' + BuiltinFunctions.NOW + ',' + BuiltinFunctions.QUARTER + ',' + BuiltinFunctions.SECOND + ',' + BuiltinFunctions.WEEK + ',' + BuiltinFunctions.YEAR; } /* * This is the string that can be used to escape '_' and '%' in a search * string pattern style catalog search parameters @return the string used to * escape wildcard characters @exception SQLException if a database access * error occurs */ public String getSearchStringEscape() throws SQLException { // Java's parse takes off two backslashes // and then pg's input parser takes off another layer // so we need many backslashes here. // // This would work differently if you used a PreparedStatement // and " mycol LIKE ? " which using the V3 protocol would skip // pg's input parser, but I don't know what we can do about that. // return "\\"; } /* * Get all the "extra" characters that can be used in unquoted identifier * names (those beyond a-zA-Z0-9 and _) <p>From the file * src/backend/parser/scan.l, an identifier is {letter}{letter_or_digit} * which makes it just those listed above. @return a string containing the * extra characters @exception SQLException if a database access error * occurs */ public String getExtraNameCharacters() throws SQLException { return ""; } /* * Is "ALTER TABLE" with an add column supported? Yes for PostgreSQL 6.1 * @return true if so @exception SQLException if a database access error * occurs */ public boolean supportsAlterTableWithAddColumn() throws SQLException { return true; } /* * Is "ALTER TABLE" with a drop column supported? @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsAlterTableWithDropColumn() throws SQLException { return true; } /* * Is column aliasing supported? <p>If so, the SQL AS clause can be used to * provide names for computed columns or to provide alias names for columns * as required. A JDBC Compliant driver always returns true. <p>e.g. <br><pre> * select count(C) as C_COUNT from T group by C; </pre><br> should return a * column named as C_COUNT instead of count(C) @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsColumnAliasing() throws SQLException { return true; } /* * Are concatenations between NULL and non-NULL values NULL? A JDBC * Compliant driver always returns true @return true if so @exception * SQLException if a database access error occurs */ public boolean nullPlusNonNullIsNull() throws SQLException { return true; } public boolean supportsConvert() throws SQLException { return false; } public boolean supportsConvert(int fromType, int toType) throws SQLException { return false; } /* * Are table correlation names supported? A JDBC Compliant driver always * returns true. @return true if so; false otherwise @exception SQLException - * if a database access error occurs */ public boolean supportsTableCorrelationNames() throws SQLException { return true; } /* * If table correlation names are supported, are they restricted to be * different from the names of the tables? @return true if so; false * otherwise @exception SQLException - if a database access error occurs */ public boolean supportsDifferentTableCorrelationNames() throws SQLException { return false; } /* * Are expressions in "ORDER BY" lists supported? <br>e.g. select * from t * order by a + b; @return true if so @exception SQLException if a database * access error occurs */ public boolean supportsExpressionsInOrderBy() throws SQLException { return true; } /* * Can an "ORDER BY" clause use columns not in the SELECT? @return true if * so @exception SQLException if a database access error occurs */ public boolean supportsOrderByUnrelated() throws SQLException { return true; } /* * Is some form of "GROUP BY" clause supported? I checked it, and yes it is. * @return true if so @exception SQLException if a database access error * occurs */ public boolean supportsGroupBy() throws SQLException { return true; } /* * Can a "GROUP BY" clause use columns not in the SELECT? @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsGroupByUnrelated() throws SQLException { return true; } /* * Can a "GROUP BY" clause add columns not in the SELECT provided it * specifies all the columns in the SELECT? Does anyone actually understand * what they mean here? (I think this is a subset of the previous function. -- * petere) @return true if so @exception SQLException if a database access * error occurs */ public boolean supportsGroupByBeyondSelect() throws SQLException { return true; } /* * Is the escape character in "LIKE" clauses supported? A JDBC compliant * driver always returns true. @return true if so @exception SQLException if * a database access error occurs */ public boolean supportsLikeEscapeClause() throws SQLException { return true; } /* * Are multiple ResultSets from a single execute supported? Well, I * implemented it, but I dont think this is possible from the back ends * point of view. @return true if so @exception SQLException if a database * access error occurs */ public boolean supportsMultipleResultSets() throws SQLException { return false; } /* * Can we have multiple transactions open at once (on different * connections?) I guess we can have, since Im relying on it. @return true * if so @exception SQLException if a database access error occurs */ public boolean supportsMultipleTransactions() throws SQLException { return true; } /* * Can columns be defined as non-nullable. A JDBC Compliant driver always * returns true. <p>This changed from false to true in v6.2 of the driver, * as this support was added to the backend. @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsNonNullableColumns() throws SQLException { return true; } /* * Does this driver support the minimum ODBC SQL grammar. This grammar is * defined at: <p><a * href="http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm">http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm</a> * <p>In Appendix C. From this description, we seem to support the ODBC * minimal (Level 0) grammar. @return true if so @exception SQLException if * a database access error occurs */ public boolean supportsMinimumSQLGrammar() throws SQLException { return true; } /* * Does this driver support the Core ODBC SQL grammar. We need SQL-92 * conformance for this. @return true if so @exception SQLException if a * database access error occurs */ public boolean supportsCoreSQLGrammar() throws SQLException { return false; } /* * Does this driver support the Extended (Level 2) ODBC SQL grammar. We * don't conform to the Core (Level 1), so we can't conform to the Extended * SQL Grammar. @return true if so @exception SQLException if a database * access error occurs */ public boolean supportsExtendedSQLGrammar() throws SQLException { return false; } /* * 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 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. @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsANSI92EntryLevelSQL() throws SQLException { return true; } /* * Does this driver support the ANSI-92 intermediate level SQL grammar? * @return true if so @exception SQLException if a database access error * occurs */ public boolean supportsANSI92IntermediateSQL() throws SQLException { return false; } /* * Does this driver support the ANSI-92 full SQL grammar? @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsANSI92FullSQL() throws SQLException { return false; } /* * Is the SQL Integrity Enhancement Facility supported? Our best guess is * that this means support for constraints @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsIntegrityEnhancementFacility() throws SQLException { return true; } /* * Is some form of outer join supported? @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsOuterJoins() throws SQLException { return true; } /* * Are full nexted outer joins supported? @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsFullOuterJoins() throws SQLException { return true; } /* * Is there limited support for outer joins? @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsLimitedOuterJoins() throws SQLException { return true; } /* * What is the database vendor's preferred term for "schema"? PostgreSQL * doesn't have schemas, but when it does, we'll use the term "schema". * @return the vendor term @exception SQLException if a database access * error occurs */ public String getSchemaTerm() throws SQLException { return "schema"; } /* * What is the database vendor's preferred term for "procedure"? * Traditionally, "function" has been used. @return the vendor term * @exception SQLException if a database access error occurs */ public String getProcedureTerm() throws SQLException { return "function"; } /* * What is the database vendor's preferred term for "catalog"? @return the * vendor term @exception SQLException if a database access error occurs */ public String getCatalogTerm() throws SQLException { return "database"; } /* * Does a catalog appear at the start of a qualified table name? (Otherwise * it appears at the end). @return true if so @exception SQLException if a * database access error occurs */ public boolean isCatalogAtStart() throws SQLException { // return true here; we return false for every other catalog function // so it won't matter what we return here D.C. return true; } /* * What is the Catalog separator. @return the catalog separator string * @exception SQLException if a database access error occurs */ public String getCatalogSeparator() throws SQLException { // Give them something to work with here // everything else returns false so it won't matter what we return here // D.C. return "."; } /* * Can a schema name be used in a data manipulation statement? @return true * if so @exception SQLException if a database access error occurs */ public boolean supportsSchemasInDataManipulation() throws SQLException { return true; } /* * Can a schema name be used in a procedure call statement? @return true if * so @exception SQLException if a database access error occurs */ public boolean supportsSchemasInProcedureCalls() throws SQLException { return true; } /* * Can a schema be used in a table definition statement? @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsSchemasInTableDefinitions() throws SQLException { return true; } /* * Can a schema name be used in an index definition statement? @return true * if so @exception SQLException if a database access error occurs */ public boolean supportsSchemasInIndexDefinitions() throws SQLException { return true; } /* * Can a schema name be used in a privilege definition statement? @return * true if so @exception SQLException if a database access error occurs */ public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { return true; } /* * Can a catalog name be used in a data manipulation statement? @return true * if so @exception SQLException if a database access error occurs */ public boolean supportsCatalogsInDataManipulation() throws SQLException { return false; } /* * Can a catalog name be used in a procedure call statement? @return true if * so @exception SQLException if a database access error occurs */ public boolean supportsCatalogsInProcedureCalls() throws SQLException { return false; } /* * Can a catalog name be used in a table definition statement? @return true * if so @exception SQLException if a database access error occurs */ public boolean supportsCatalogsInTableDefinitions() throws SQLException { return false; } /* * Can a catalog name be used in an index definition? @return true if so * @exception SQLException if a database access error occurs */ public boolean supportsCatalogsInIndexDefinitions() throws SQLException { return false; } /* * Can a catalog name be used in a privilege definition statement? @return * true if so @exception SQLException if a database access error occurs */ public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { return false; } /* * We support cursors for gets only it seems. I dont see a method to get a * positioned delete. @return true if so @exception SQLException if a * database access error occurs */ public boolean supportsPositionedDelete() throws SQLException { return false; // For now... } /* * Is positioned UPDATE supported? @return true if so @exception * SQLException if a database access error occurs */ public boolean supportsPositionedUpdate() throws SQLException { return false; // For now... } /* * Is SELECT for UPDATE supported? @return true if so; false otherwise * @exception SQLException - if a database access error occurs */ public boolean supportsSelectForUpdate() throws SQLException { return true; } /* * Are stored procedure calls using the stored procedure escape syntax * supported? @return true if so; false otherwise @exception SQLException - * if a database access error occurs */ public boolean supportsStoredProcedures() throws SQLException { return false; } /* * Are subqueries in comparison expressions supported? A JDBC Compliant * driver always returns true. @return true if so; false otherwise * @exception SQLException - if a database access error occurs */ public boolean supportsSubqueriesInComparisons() throws SQLException { return true; } /* * Are subqueries in 'exists' expressions supported? A JDBC Compliant driver * always returns true. @return true if so; false otherwise @exception * SQLException - if a database access error occurs */ public boolean supportsSubqueriesInExists() throws SQLException { return true; } /* * Are subqueries in 'in' statements supported? A JDBC Compliant driver * always returns true. @return true if so; false otherwise @exception * SQLException - if a database access error occurs */ public boolean supportsSubqueriesInIns() throws SQLException { return true; } /* * Are subqueries in quantified expressions supported? A JDBC Compliant * driver always returns true. (No idea what this is, but we support a good * deal of subquerying.) @return true if so; false otherwise @exception * SQLException - if a database access error occurs */ public boolean supportsSubqueriesInQuantifieds() throws SQLException { return true; } /* * Are correlated subqueries supported? A JDBC Compliant driver always * returns true. (a.k.a. subselect in from?) @return true if so; false * otherwise @exception SQLException - if a database access error occurs */ public boolean supportsCorrelatedSubqueries() throws SQLException { return true; } /* * Is SQL UNION supported? @return true if so @exception SQLException if a * database access error occurs */ public boolean supportsUnion() throws SQLException { return true; } /* * Is SQL UNION ALL supported? @return true if so @exception SQLException if * a database access error occurs */ public boolean supportsUnionAll() throws SQLException { return true; } /* * In PostgreSQL, Cursors are only open within transactions. @return true if * so @exception SQLException if a database access error occurs */ public boolean supportsOpenCursorsAcrossCommit() throws SQLException { return false; } /* * Do we support open cursors across multiple transactions? @return true if * so @exception SQLException if a database access error occurs */ public boolean supportsOpenCursorsAcrossRollback() throws SQLException { return false; } /* * Can statements remain open across commits? They may, but this driver * cannot guarentee that. In further reflection. we are talking a Statement * object here, so the answer is yes, since the Statement is only a vehicle * to ExecSQL() @return true if they always remain open; false otherwise * @exception SQLException if a database access error occurs */ public boolean supportsOpenStatementsAcrossCommit() throws SQLException { return true; } /* * Can statements remain open across rollbacks? They may, but this driver * cannot guarentee that. In further contemplation, we are talking a * Statement object here, so the answer is yes, since the Statement is only * a vehicle to ExecSQL() in Connection @return true if they always remain * open; false otherwise @exception SQLException if a database access error * occurs */ public boolean supportsOpenStatementsAcrossRollback() throws SQLException { return true; } /* * How many hex characters can you have in an inline binary literal @return * the max literal length @exception SQLException if a database access error * occurs */ public int getMaxBinaryLiteralLength() throws SQLException { return 0; // no limit } /* * What is the maximum length for a character literal I suppose it is 8190 * (8192 - 2 for the quotes) @return the max literal length @exception * SQLException if a database access error occurs */ public int getMaxCharLiteralLength() throws SQLException { return 0; // no limit } /* * Whats the limit on column name length. @return the maximum column name * length @exception SQLException if a database access error occurs */ public int getMaxColumnNameLength() throws SQLException { return getMaxNameLength(); } /* * What is the maximum number of columns in a "GROUP BY" clause? @return the * max number of columns @exception SQLException if a database access error * occurs */ public int getMaxColumnsInGroupBy() throws SQLException { return 0; // no limit } /* * What's the maximum number of columns allowed in an index? @return max * number of columns @exception SQLException if a database access error * occurs */ public int getMaxColumnsInIndex() throws SQLException { return getMaxIndexKeys(); } /* * What's the maximum number of columns in an "ORDER BY clause? @return the * max columns @exception SQLException if a database access error occurs */ public int getMaxColumnsInOrderBy() throws SQLException { return 0; // no limit } /* * What is the maximum number of columns in a "SELECT" list? @return the max * columns @exception SQLException if a database access error occurs */ public int getMaxColumnsInSelect() throws SQLException { return 0; // no limit } /* * What is the maximum number of columns in a table? From the CREATE TABLE * reference page... <p>"The new class is created as a heap with no initial * data. A class can have no more than 1600 attributes (realistically, this * is limited by the fact that tuple sizes must be less than 8192 bytes)..." * @return the max columns @exception SQLException if a database access * error occurs */ public int getMaxColumnsInTable() throws SQLException { return 1600; } /* * How many active connection can we have at a time to this database? Well, * since it depends on postmaster, which just does a listen() followed by an * accept() and fork(), its basically very high. Unless the system runs out * of processes, it can be 65535 (the number of aux. ports on a TCP/IP * system). I will return 8192 since that is what even the largest system * can realistically handle, @return the maximum number of connections * @exception SQLException if a database access error occurs */ public int getMaxConnections() throws SQLException { return 8192; } /* * What is the maximum cursor name length @return max cursor name length in * bytes @exception SQLException if a database access error occurs */ public int getMaxCursorNameLength() throws SQLException { return getMaxNameLength(); } /* * Retrieves the maximum number of bytes for an index, including all of the * parts of the index. @return max index length in bytes, which includes the * composite of all the constituent parts of the index; a result of zero * means that there is no limit or the limit is not known @exception * SQLException if a database access error occurs */ public int getMaxIndexLength() throws SQLException { return 0; // no limit (larger than an int anyway) } public int getMaxSchemaNameLength() throws SQLException { return getMaxNameLength(); } /* * What is the maximum length of a procedure name @return the max name * length in bytes @exception SQLException if a database access error occurs */ public int getMaxProcedureNameLength() throws SQLException { return getMaxNameLength(); } public int getMaxCatalogNameLength() throws SQLException { return getMaxNameLength(); } /* * What is the maximum length of a single row? @return max row size in bytes * @exception SQLException if a database access error occurs */ public int getMaxRowSize() throws SQLException { return 1073741824; // 1 GB } /* * Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY blobs? We don't * handle blobs yet @return true if so @exception SQLException if a database * access error occurs */ public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { return false; } /* * What is the maximum length of a SQL statement? @return max length in * bytes @exception SQLException if a database access error occurs */ public int getMaxStatementLength() throws SQLException { return 0; // actually whatever fits in size_t } /* * How many active statements can we have open at one time to this database? * Basically, since each Statement downloads the results as the query is * executed, we can have many. However, we can only really have one * statement per connection going at once (since they are executed serially) - * so we return one. @return the maximum @exception SQLException if a * database access error occurs */ public int getMaxStatements() throws SQLException { return 1; } /* * What is the maximum length of a table name @return max name length in * bytes @exception SQLException if a database access error occurs */ public int getMaxTableNameLength() throws SQLException { return getMaxNameLength(); } /* * What is the maximum number of tables that can be specified in a SELECT? * @return the maximum @exception SQLException if a database access error * occurs */ public int getMaxTablesInSelect() throws SQLException { return 0; // no limit } /* * What is the maximum length of a user name @return the max name length in * bytes @exception SQLException if a database access error occurs */ public int getMaxUserNameLength() throws SQLException { return getMaxNameLength(); } /* * What is the database's default transaction isolation level? We do not * support this, so all transactions are SERIALIZABLE. @return the default * isolation level @exception SQLException if a database access error occurs * * @see Connection */ public int getDefaultTransactionIsolation() throws SQLException { return Connection.TRANSACTION_READ_COMMITTED; } /* * Are transactions supported? If not, commit and rollback are noops and the * isolation level is TRANSACTION_NONE. We do support transactions. @return * true if transactions are supported @exception SQLException if a database * access error occurs */ public boolean supportsTransactions() throws SQLException { return true; } /* * Does the database support the given transaction isolation level? We only * support TRANSACTION_SERIALIZABLE and TRANSACTION_READ_COMMITTED @param * level the values are defined in java.sql.Connection @return true if so * @exception SQLException if a database access error occurs * * @see Connection */ public boolean supportsTransactionIsolationLevel(int level) throws SQLException { if(level == Connection.TRANSACTION_SERIALIZABLE || level == Connection.TRANSACTION_READ_COMMITTED) return true; if(this.getDatabaseMajorVersion() >= 8 && (level == Connection.TRANSACTION_READ_UNCOMMITTED || level == Connection.TRANSACTION_REPEATABLE_READ)) return true; return false; } /* * Are both data definition and data manipulation transactions supported? * @return true if so @exception SQLException if a database access error * occurs */ public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { return true; } /* * Are only data manipulation statements withing a transaction supported? * @return true if so @exception SQLException if a database access error * occurs */ public boolean supportsDataManipulationTransactionsOnly() throws SQLException { return false; } /* * Does a data definition statement within a transaction force the * transaction to commit? I think this means something like: <p><pre> * CREATE TABLE T (A INT); INSERT INTO T (A) VALUES (2); BEGIN; UPDATE T SET * A = A + 1; CREATE TABLE X (A INT); SELECT A FROM T INTO X; COMMIT; </pre><p> * does the CREATE TABLE call cause a commit? The answer is no. @return true * if so @exception SQLException if a database access error occurs */ public boolean dataDefinitionCausesTransactionCommit() throws SQLException { return false; } /* * Is a data definition statement within a transaction ignored? @return true * if so @exception SQLException if a database access error occurs */ public boolean dataDefinitionIgnoredInTransactions() throws SQLException { return false; } /** * Escape single quotes with another single quote. */ private static String escapeQuotes(String s) { if (s == null) { return null; } StringBuffer sb = new StringBuffer(); int length = s.length(); char prevChar = ' '; char prevPrevChar = ' '; for(int i = 0; i < length; i++) { char c = s.charAt(i); sb.append(c); if(c == '\'' && (prevChar != '\\' || (prevChar == '\\' && prevPrevChar == '\\'))) { sb.append("'"); } prevPrevChar = prevChar; prevChar = c; } return sb.toString(); } /** * Creates a condition with the specified operator * based on schema specification:<BR> * <UL> * <LI>schema is specified => search in this schema only</LI> * <LI>schema is equal to "" => search in the 'public' schema</LI> * <LI>schema is null => search in all schemas</LI> * </UL> */ private static String resolveSchemaConditionWithOperator( String expr, String schema, String operator) { //schema is null => search in current_schemas(true) if (schema == null) { //This means that only "visible" schemas are searched. //It was approved to change to *all* schemas. //return expr + " " + operator + " ANY (current_schemas(true))"; return "1=1"; } //schema is specified => search in this schema else if(!"".equals(schema)) { return expr + " " + operator + " '" + escapeQuotes(schema) + "' "; } //schema is "" => search in the 'public' schema else { return expr + " " + operator + " 'public' "; } } /** * Creates an equality condition based on schema specification:<BR> * <UL> * <LI>schema is specified => search in this schema only</LI> * <LI>schema is equal to "" => search in the 'public' schema</LI> * <LI>schema is null => search in all schemas</LI> * </UL> */ private static String resolveSchemaCondition(String expr, String schema) { return resolveSchemaConditionWithOperator(expr, schema, "="); } /** * Creates a pattern condition based on schema specification:<BR> * <UL> * <LI>schema is specified => search in this schema only</LI> * <LI>schema is equal to "" => search in the 'public' schema</LI> * <LI>schema is null => search in all schemas</LI> * </UL> */ private static String resolveSchemaPatternCondition( String expr, String schema) { return resolveSchemaConditionWithOperator(expr, schema, "LIKE"); } /* * Get a description of stored procedures available in a catalog <p>Only * procedure descriptions matching the schema and procedure name criteria * are returned. They are ordered by PROCEDURE_SCHEM and PROCEDURE_NAME <p>Each * procedure description has the following columns: <ol> <li><b>PROCEDURE_CAT</b> * String => procedure catalog (may be null) <li><b>PROCEDURE_SCHEM</b> * String => procedure schema (may be null) <li><b>PROCEDURE_NAME</b> * String => procedure name <li><b>ResultSetField 4</b> reserved (make it * null) <li><b>ResultSetField 5</b> reserved (make it null) <li><b>ResultSetField * 6</b> reserved (make it null) <li><b>REMARKS</b> String => explanatory * comment on the procedure <li><b>PROCEDURE_TYPE</b> short => kind of * procedure <ul> <li> procedureResultUnknown - May return a result <li> * procedureNoResult - Does not return a result <li> procedureReturnsResult - * Returns a result </ul> </ol> @param catalog - a catalog name; "" * retrieves those without a catalog; null means drop catalog name from * criteria @param schemaParrern - a schema name pattern; "" retrieves those * without a schema - we ignore this parameter @param procedureNamePattern - * a procedure name pattern @return ResultSet - each row is a procedure * description @exception SQLException if a database access error occurs */ public java.sql.ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { String sql = "SELECT NULL AS PROCEDURE_CAT, n.nspname AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, d.description AS REMARKS, " + java.sql.DatabaseMetaData.procedureReturnsResult + " AS PROCEDURE_TYPE " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_proc p " + " LEFT JOIN pg_catalog.pg_description d ON (p.oid=d.objoid) " + " LEFT JOIN pg_catalog.pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') " + " LEFT JOIN pg_catalog.pg_namespace pn ON (c.relnamespace=pn.oid AND pn.nspname='pg_catalog') " + " WHERE p.pronamespace=n.oid " + " AND " + resolveSchemaPatternCondition( "n.nspname", schemaPattern); if(procedureNamePattern != null) { sql += " AND p.proname LIKE '" + escapeQuotes(procedureNamePattern) + "' "; } sql += " ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME "; return createMetaDataStatement().executeQuery(sql); } /* * Get a description of a catalog's stored procedure parameters and result * columns. <p>Only descriptions matching the schema, procedure and * parameter name criteria are returned. They are ordered by PROCEDURE_SCHEM * and PROCEDURE_NAME. Within this, the return value, if any, is first. Next * are the parameter descriptions in call order. The column descriptions * follow in column number order. <p>Each row in the ResultSet is a * parameter description or column description with the following fields: * <ol> <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) * <li><b>PROCEDURE_SCHE</b>M String => procedure schema (may be null) * <li><b>PROCEDURE_NAME</b> String => procedure name <li><b>COLUMN_NAME</b> * String => column/parameter name <li><b>COLUMN_TYPE</b> Short => kind of * column/parameter: <ul><li>procedureColumnUnknown - nobody knows <li>procedureColumnIn - * IN parameter <li>procedureColumnInOut - INOUT parameter <li>procedureColumnOut - * OUT parameter <li>procedureColumnReturn - procedure return value <li>procedureColumnResult - * result column in ResultSet </ul> <li><b>DATA_TYPE</b> short => SQL type * from java.sql.Types <li><b>TYPE_NAME</b> String => Data source specific * type name <li><b>PRECISION</b> int => precision <li><b>LENGTH</b> int => * length in bytes of data <li><b>SCALE</b> short => scale <li><b>RADIX</b> * short => radix <li><b>NULLABLE</b> short => can it contain NULL? <ul><li>procedureNoNulls - * does not allow NULL values <li>procedureNullable - allows NULL values * <li>procedureNullableUnknown - nullability unknown <li><b>REMARKS</b> * String => comment describing parameter/column </ol> @param catalog This * is ignored in org.postgresql, advise this is set to null @param * schemaPattern @param procedureNamePattern a procedure name pattern @param * columnNamePattern a column name pattern, this is currently ignored * because postgresql does not name procedure parameters. @return each row * is a stored procedure parameter or column description @exception * SQLException if a database-access error occurs * * @see #getSearchStringEscape */ // Implementation note: This is required for Borland's JBuilder to work public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { ResultSetField f[] = new ResultSetField[13]; ArrayList v = new ArrayList(); // The new ResultSet tuple stuff f[0] = new ResultSetField("PROCEDURE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("PROCEDURE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("PROCEDURE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[3] = new ResultSetField("COLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("COLUMN_TYPE", TypeOid.INT2, 2); f[5] = new ResultSetField("DATA_TYPE", TypeOid.INT2, 2); f[6] = new ResultSetField("TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[7] = new ResultSetField("PRECISION", TypeOid.INT4, 4); f[8] = new ResultSetField("LENGTH", TypeOid.INT4, 4); f[9] = new ResultSetField("SCALE", TypeOid.INT2, 2); f[10] = new ResultSetField("RADIX", TypeOid.INT2, 2); f[11] = new ResultSetField("NULLABLE", TypeOid.INT2, 2); f[12] = new ResultSetField("REMARKS", TypeOid.VARCHAR, getMaxNameLength()); String sql = "SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype::varchar,t.typrelid " + " 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 " + " AND " + resolveSchemaPatternCondition( "n.nspname", schemaPattern); if(procedureNamePattern != null) { sql += " AND p.proname LIKE '" + escapeQuotes(procedureNamePattern) + "' "; } sql += " ORDER BY n.nspname, p.proname "; ResultSet rs = m_connection.createStatement().executeQuery(sql); String schema = null; String procedureName = null; Oid returnType = null; String returnTypeType = null; Oid returnTypeRelid = null; Oid[] argTypes = null; while(rs.next()) { schema = rs.getString("nspname"); procedureName = rs.getString("proname"); returnType = (Oid)rs.getObject("prorettype"); returnTypeType = rs.getString("typtype"); returnTypeRelid = (Oid)rs.getObject("typrelid"); argTypes = (Oid[])rs.getObject("proargtypes"); // decide if we are returning a single column result. if(!returnTypeType.equals("c")) { Object[] tuple = new Object[13]; tuple[0] = null; tuple[1] = schema; tuple[2] = procedureName; tuple[3] = "returnValue"; tuple[4] = new Short((short)java.sql.DatabaseMetaData.procedureColumnReturn); tuple[5] = new Short((short)m_connection.getSQLType(returnType)); tuple[6] = m_connection.getPGType(returnType); tuple[7] = null; tuple[8] = null; tuple[9] = null; tuple[10] = null; tuple[11] = new Short((short)java.sql.DatabaseMetaData.procedureNullableUnknown); tuple[12] = null; v.add(tuple); } // Add a row for each argument. for(int i = 0; i < argTypes.length; i++) { Oid argOid = argTypes[i]; Object[] tuple = new Object[13]; tuple[0] = null; tuple[1] = schema; tuple[2] = procedureName; tuple[3] = "$" + (i + 1); tuple[4] = new Short((short)java.sql.DatabaseMetaData.procedureColumnIn); tuple[5] = new Short((short)m_connection.getSQLType(argOid)); tuple[6] = m_connection.getPGType(argOid); tuple[7] = null; tuple[8] = null; tuple[9] = null; tuple[10] = null; tuple[11] = new Short((short)java.sql.DatabaseMetaData.procedureNullableUnknown); tuple[12] = null; v.add(tuple); } // if we are returning a multi-column result. if(returnTypeType.equals("c")) { String columnsql = "SELECT a.attname,a.atttypid FROM pg_catalog.pg_attribute a WHERE a.attrelid = ? ORDER BY a.attnum "; PreparedStatement stmt = m_connection.prepareStatement(columnsql); stmt.setObject(1, returnTypeRelid); ResultSet columnrs = stmt.executeQuery(columnsql); while(columnrs.next()) { Oid columnTypeOid = (Oid)columnrs.getObject("atttypid"); Object[] tuple = new Object[13]; tuple[0] = null; tuple[1] = schema; tuple[2] = procedureName; tuple[3] = columnrs.getString("attname"); tuple[4] = new Short((short)java.sql.DatabaseMetaData.procedureColumnResult); tuple[5] = new Short((short)m_connection.getSQLType(columnTypeOid)); tuple[6] = m_connection.getPGType(columnTypeOid); tuple[7] = null; tuple[8] = null; tuple[9] = null; tuple[10] = null; tuple[11] = new Short((short)java.sql.DatabaseMetaData.procedureNullableUnknown); tuple[12] = null; v.add(tuple); } columnrs.close(); stmt.close(); } } rs.close(); return createSyntheticResultSet(f, v); } /* * Get a description of tables available in a catalog. <p>Only table * descriptions matching the catalog, schema, table name and type criteria * are returned. They are ordered by TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. * <p>Each table description has the following columns: <ol> <li><b>TABLE_CAT</b> * String => table catalog (may be null) <li><b>TABLE_SCHEM</b> String => * table schema (may be null) <li><b>TABLE_NAME</b> String => table name * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE", * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", * "SYNONYM". <li><b>REMARKS</b> String => explanatory comment on the * table </ol> <p>The valid values for the types parameter are: "TABLE", * "INDEX", "SEQUENCE", "VIEW", "SYSTEM TABLE", "SYSTEM INDEX", "SYSTEM * VIEW", "SYSTEM TOAST TABLE", "SYSTEM TOAST INDEX", "TEMPORARY TABLE", and * "TEMPORARY VIEW" @param catalog a catalog name; For org.postgresql, this * is ignored, and should be set to null @param schemaPattern a schema name * pattern @param tableNamePattern a table name pattern. For all tables this * should be "%" @param types a list of table types to include; null returns * all types @return each row is a table description @exception SQLException * if a database-access error occurs. */ public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException { String useSchemas = "SCHEMAS"; String select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME, " + " CASE n.nspname LIKE 'pg!_%' ESCAPE '!' OR n.nspname = 'information_schema' " + " WHEN true THEN CASE " + " WHEN n.nspname = 'pg_catalog' OR n.nspname = 'information_schema' THEN CASE c.relkind " + " WHEN 'r' THEN 'SYSTEM TABLE' " + " WHEN 'v' THEN 'SYSTEM VIEW' " + " WHEN 'i' THEN 'SYSTEM INDEX' " + " ELSE NULL " + " END " + " WHEN n.nspname = 'pg_toast' THEN CASE c.relkind " + " WHEN 'r' THEN 'SYSTEM TOAST TABLE' " + " WHEN 'i' THEN 'SYSTEM TOAST INDEX' " + " ELSE NULL " + " END " + " ELSE CASE c.relkind " + " WHEN 'r' THEN 'TEMPORARY TABLE' " + " WHEN 'i' THEN 'TEMPORARY INDEX' " + " ELSE NULL " + " END " + " END " + " WHEN false THEN CASE c.relkind " + " WHEN 'r' THEN 'TABLE' " + " WHEN 'i' THEN 'INDEX' " + " WHEN 'S' THEN 'SEQUENCE' " + " WHEN 'v' THEN 'VIEW' " + " ELSE NULL " + " END " + " ELSE NULL " + " END " + " AS TABLE_TYPE, d.description AS REMARKS " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c " + " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0) " + " LEFT JOIN pg_catalog.pg_class dc ON (d.classoid=dc.oid AND dc.relname='pg_class') " + " LEFT JOIN pg_catalog.pg_namespace dn ON (dn.oid=dc.relnamespace AND dn.nspname='pg_catalog') " + " WHERE c.relnamespace = n.oid " + " AND " + resolveSchemaPatternCondition( "n.nspname", schemaPattern); String orderby = " ORDER BY TABLE_TYPE,TABLE_SCHEM,TABLE_NAME "; if(types == null) { types = s_defaultTableTypes; } if(tableNamePattern != null) { select += " AND c.relname LIKE '" + escapeQuotes(tableNamePattern) + "' "; } String sql = select; sql += " AND (false "; for(int i = 0; i < types.length; i++) { HashMap clauses = (HashMap)s_tableTypeClauses.get(types[i]); if(clauses != null) { String clause = (String)clauses.get(useSchemas); sql += " OR ( " + clause + " ) "; } } sql += ") "; sql += orderby; return createMetaDataStatement().executeQuery(sql); } private static final HashMap s_tableTypeClauses; static { s_tableTypeClauses = new HashMap(); HashMap ht = new HashMap(); s_tableTypeClauses.put("TABLE", ht); ht.put("SCHEMAS", "c.relkind = 'r' AND n.nspname NOT LIKE 'pg!_%' ESCAPE '!' AND n.nspname <> 'information_schema'"); ht.put("NOSCHEMAS", "c.relkind = 'r' AND c.relname NOT LIKE 'pg!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("VIEW", ht); ht.put("SCHEMAS", "c.relkind = 'v' AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema'"); ht.put("NOSCHEMAS", "c.relkind = 'v' AND c.relname NOT LIKE 'pg!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("INDEX", ht); ht.put("SCHEMAS", "c.relkind = 'i' AND n.nspname NOT LIKE 'pg!_%' ESCAPE '!' AND n.nspname <> 'information_schema'"); ht.put("NOSCHEMAS", "c.relkind = 'i' AND c.relname NOT LIKE 'pg!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("SEQUENCE", ht); ht.put("SCHEMAS", "c.relkind = 'S'"); ht.put("NOSCHEMAS", "c.relkind = 'S'"); ht = new HashMap(); s_tableTypeClauses.put("SYSTEM TABLE", ht); ht.put("SCHEMAS", "c.relkind = 'r' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema')"); ht.put("NOSCHEMAS", "c.relkind = 'r' AND c.relname LIKE 'pg!_%' ESCAPE '!' AND c.relname NOT LIKE 'pgLIKE 'pg!_toast!_%' ESCAPE '!'toast!_%' ESCAPE '!' AND c.relname NOT LIKE 'pg!_temp!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("SYSTEM TOAST TABLE", ht); ht.put("SCHEMAS", "c.relkind = 'r' AND n.nspname = 'pg_toast'"); ht.put("NOSCHEMAS", "c.relkind = 'r' AND c.relname LIKE 'pg!_toast!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("SYSTEM TOAST INDEX", ht); ht.put("SCHEMAS", "c.relkind = 'i' AND n.nspname = 'pg_toast'"); ht.put("NOSCHEMAS", "c.relkind = 'i' AND c.relname LIKE 'pg!_toast!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("SYSTEM VIEW", ht); ht.put("SCHEMAS", "c.relkind = 'v' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); ht.put("NOSCHEMAS", "c.relkind = 'v' AND c.relname LIKE 'pg!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("SYSTEM INDEX", ht); ht.put("SCHEMAS", "c.relkind = 'i' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); ht.put("NOSCHEMAS", "c.relkind = 'v' AND c.relname LIKE 'pg!_%' ESCAPE '!' AND c.relname NOT LIKE 'pg!_toast!_%' ESCAPE '!' AND c.relname NOT LIKE 'pg!_temp!_%' ESCAPE '!'"); ht = new HashMap(); s_tableTypeClauses.put("TEMPORARY TABLE", ht); ht.put("SCHEMAS", "c.relkind = 'r' AND n.nspname LIKE 'pg!_temp!_%' ESCAPE '!' "); ht.put("NOSCHEMAS", "c.relkind = 'r' AND c.relname LIKE 'pg!_temp!_%' ESCAPE '!' "); ht = new HashMap(); s_tableTypeClauses.put("TEMPORARY INDEX", ht); ht.put("SCHEMAS", "c.relkind = 'i' AND n.nspname LIKE 'pg!_temp!_%' ESCAPE '!' "); ht.put("NOSCHEMAS", "c.relkind = 'i' AND c.relname LIKE 'pg!_temp!_%' ESCAPE '!' "); } // These are the default tables, used when NULL is passed to getTables // The choice of these provide the same behaviour as psql's \d private static final String s_defaultTableTypes[] = { "TABLE", "VIEW", "INDEX", "SEQUENCE", "TEMPORARY TABLE" }; /* * Get the schema names available in this database. The results are ordered * by schema name. <P>The schema column is: <OL> <LI><B>TABLE_SCHEM</B> * String => schema name </OL> @return ResultSet each row has a single * String column that is a schema name */ public java.sql.ResultSet getSchemas() throws SQLException { String sql = "SELECT nspname AS TABLE_SCHEM FROM pg_catalog.pg_namespace WHERE nspname <> 'pg_toast' AND nspname NOT LIKE 'pg!_temp!_%' ESCAPE '!' ORDER BY TABLE_SCHEM"; return createMetaDataStatement().executeQuery(sql); } /* * Get the catalog names available in this database. The results are ordered * by catalog name. <P>The catalog column is: <OL> <LI><B>TABLE_CAT</B> * String => catalog name </OL> @return ResultSet each row has a single * String column that is a catalog name */ public java.sql.ResultSet getCatalogs() throws SQLException { String sql = "SELECT datname AS TABLE_CAT FROM pg_catalog.pg_database ORDER BY TABLE_CAT"; return createMetaDataStatement().executeQuery(sql); } /* * Get the table types available in this database. The results are ordered * by table type. <P>The table type is: <OL> <LI><B>TABLE_TYPE</B> String => * table type. Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL * TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". </OL> @return * ResultSet each row has a single String column that is a table type */ public java.sql.ResultSet getTableTypes() throws SQLException { String types[] = (String[])s_tableTypeClauses.keySet().toArray(new String[s_tableTypeClauses.size()]); sortStringArray(types); ResultSetField f[] = new ResultSetField[1]; ArrayList v = new ArrayList(); f[0] = new ResultSetField(new String("TABLE_TYPE"), TypeOid.VARCHAR, getMaxNameLength()); for(int i = 0; i < types.length; i++) { Object[] tuple = new Object[1]; tuple[0] = types[i]; v.add(tuple); } return createSyntheticResultSet(f, v); } /* * Get a description of table columns available in a catalog. <P>Only * column descriptions matching the catalog, schema, table and column name * criteria are returned. They are ordered by TABLE_SCHEM, TABLE_NAME and * ORDINAL_POSITION. <P>Each column description has the following columns: * <OL> <LI><B>TABLE_CAT</B> String => table catalog (may be null) <LI><B>TABLE_SCHEM</B> * String => table schema (may be null) <LI><B>TABLE_NAME</B> String => * table name <LI><B>COLUMN_NAME</B> String => column name <LI><B>DATA_TYPE</B> * short => SQL type from java.sql.Types <LI><B>TYPE_NAME</B> String => * Data source dependent type name <LI><B>COLUMN_SIZE</B> int => column * size. For char or date types this is the maximum number of characters, * for numeric or decimal types this is precision. <LI><B>BUFFER_LENGTH</B> * is not used. <LI><B>DECIMAL_DIGITS</B> int => the number of fractional * digits <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or * 2) <LI><B>NULLABLE</B> int => is NULL allowed? <UL> <LI> columnNoNulls - * might not allow NULL values <LI> columnNullable - definitely allows NULL * values <LI> columnNullableUnknown - nullability unknown </UL> <LI><B>REMARKS</B> * String => comment describing column (may be null) <LI><B>COLUMN_DEF</B> * String => default value (may be null) <LI><B>SQL_DATA_TYPE</B> int => * unused <LI><B>SQL_DATETIME_SUB</B> int => unused <LI><B>CHAR_OCTET_LENGTH</B> * int => for char types the maximum number of bytes in the column <LI><B>ORDINAL_POSITION</B> * int => index of column in table (starting at 1) <LI><B>IS_NULLABLE</B> * String => "NO" means column definitely does not allow NULL values; "YES" * means the column might allow NULL values. An empty string means nobody * knows. </OL> @param catalog a catalog name; "" retrieves those without a * catalog @param schemaPattern a schema name pattern; "" retrieves those * without a schema @param tableNamePattern a table name pattern @param * columnNamePattern a column name pattern @return ResultSet each row is a * column description * * @see #getSearchStringEscape */ public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { ArrayList v = new ArrayList(); // The new ResultSet tuple stuff ResultSetField f[] = new ResultSetField[18]; // The field descriptors // for the new ResultSet f[0] = new ResultSetField("TABLE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("TABLE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("TABLE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[3] = new ResultSetField("COLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("DATA_TYPE", TypeOid.INT2, 2); f[5] = new ResultSetField("TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[6] = new ResultSetField("COLUMN_SIZE", TypeOid.INT4, 4); f[7] = new ResultSetField("BUFFER_LENGTH", TypeOid.VARCHAR, getMaxNameLength()); f[8] = new ResultSetField("DECIMAL_DIGITS", TypeOid.INT4, 4); f[9] = new ResultSetField("NUM_PREC_RADIX", TypeOid.INT4, 4); f[10] = new ResultSetField("NULLABLE", TypeOid.INT4, 4); f[11] = new ResultSetField("REMARKS", TypeOid.VARCHAR, getMaxNameLength()); f[12] = new ResultSetField("COLUMN_DEF", TypeOid.VARCHAR, getMaxNameLength()); f[13] = new ResultSetField("SQL_DATA_TYPE", TypeOid.INT4, 4); f[14] = new ResultSetField("SQL_DATETIME_SUB", TypeOid.INT4, 4); f[15] = new ResultSetField("CHAR_OCTET_LENGTH", TypeOid.INT4, 4); f[16] = new ResultSetField("ORDINAL_POSITION", TypeOid.INT4, 4); f[17] = new ResultSetField("IS_NULLABLE", TypeOid.VARCHAR, getMaxNameLength()); String sql = "SELECT n.nspname,c.relname,a.attname," + " a.atttypid as atttypid,a.attnotnull,a.atttypmod," + " a.attlen::int4 as attlen,a.attnum,def.adsrc,dsc.description " + " FROM pg_catalog.pg_namespace n " + " JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) " + " JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) " + " LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) " + " LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) " + " LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') " + " LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') " + " WHERE a.attnum > 0 AND NOT a.attisdropped " + " AND " + resolveSchemaPatternCondition( "n.nspname", schemaPattern); if(tableNamePattern != null && !"".equals(tableNamePattern)) { sql += " AND c.relname LIKE '" + escapeQuotes(tableNamePattern) + "' "; } if(columnNamePattern != null && !"".equals(columnNamePattern)) { sql += " AND a.attname LIKE '" + escapeQuotes(columnNamePattern) + "' "; } sql += " ORDER BY nspname,relname,attnum "; ResultSet rs = m_connection.createStatement().executeQuery(sql); while(rs.next()) { Object[] tuple = new Object[18]; Oid typeOid = (Oid)rs.getObject("atttypid"); tuple[0] = null; // Catalog name, not supported tuple[1] = rs.getString("nspname"); // Schema tuple[2] = rs.getString("relname"); // Table name tuple[3] = rs.getString("attname"); // Column name tuple[4] = new Short((short)m_connection.getSQLType(typeOid)); String pgType = m_connection.getPGType(typeOid); tuple[5] = m_connection.getPGType(typeOid); // Type name String defval = rs.getString("adsrc"); if(defval != null) { if(pgType.equals("int4")) { if(defval.indexOf("nextval(") != -1) tuple[5] = "serial"; // Type name == // serial } else if(pgType.equals("int8")) { if(defval.indexOf("nextval(") != -1) tuple[5] = "bigserial"; // Type name == // bigserial } } // by default no decimal_digits // if the type is numeric or decimal we will // overwrite later. tuple[8] = new Integer(0); if(pgType.equals("bpchar") || pgType.equals("varchar")) { int atttypmod = rs.getInt("atttypmod"); tuple[6] = new Integer(atttypmod != -1 ? atttypmod - VARHDRSZ : 0); } else if(pgType.equals("numeric") || pgType.equals("decimal")) { int attypmod = rs.getInt("atttypmod") - VARHDRSZ; tuple[6] = new Integer ((attypmod >> 16) & 0xffff); tuple[8] = new Integer (attypmod & 0xffff); tuple[9] = new Integer(10); } else if(pgType.equals("bit") || pgType.equals("varbit")) { tuple[6] = rs.getObject("atttypmod"); tuple[9] = new Integer(2); } else { tuple[6] = rs.getObject("attlen"); tuple[9] = new Integer(10); } tuple[7] = null; // Buffer length tuple[10] = new Integer(rs .getBoolean("attnotnull") ? java.sql.DatabaseMetaData.columnNoNulls : java.sql.DatabaseMetaData.columnNullable); // Nullable tuple[11] = rs.getString("description"); // Description (if any) tuple[12] = rs.getString("adsrc"); // 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] = new Integer(rs.getInt("attnum")); // ordinal position tuple[17] = rs.getBoolean("attnotnull") ? "NO" : "YES"; // Is // nullable v.add(tuple); } rs.close(); return createSyntheticResultSet(f, v); } /* * Get a description of the access rights for a table's columns. <P>Only * privileges matching the column name criteria are returned. They are * ordered by COLUMN_NAME and PRIVILEGE. <P>Each privilige description has * the following columns: <OL> <LI><B>TABLE_CAT</B> String => table * catalog (may be null) <LI><B>TABLE_SCHEM</B> String => table schema * (may be null) <LI><B>TABLE_NAME</B> String => table name <LI><B>COLUMN_NAME</B> * String => column name <LI><B>GRANTOR</B> => grantor of access (may be * null) <LI><B>GRANTEE</B> String => grantee of access <LI><B>PRIVILEGE</B> * String => name of access (SELECT, INSERT, UPDATE, REFRENCES, ...) <LI><B>IS_GRANTABLE</B> * String => "YES" if grantee is permitted to grant to others; "NO" if not; * null if unknown </OL> @param catalog a catalog name; "" retrieves those * without a catalog @param schema a schema name; "" retrieves those without * a schema @param table a table name @param columnNamePattern a column name * pattern @return ResultSet each row is a column privilege description * * @see #getSearchStringEscape */ public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { ResultSetField f[] = new ResultSetField[8]; ArrayList v = new ArrayList(); if(table == null) table = "%"; if(columnNamePattern == null) columnNamePattern = "%"; f[0] = new ResultSetField("TABLE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("TABLE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("TABLE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[3] = new ResultSetField("COLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("GRANTOR", TypeOid.VARCHAR, getMaxNameLength()); f[5] = new ResultSetField("GRANTEE", TypeOid.VARCHAR, getMaxNameLength()); f[6] = new ResultSetField("PRIVILEGE", TypeOid.VARCHAR, getMaxNameLength()); f[7] = new ResultSetField("IS_GRANTABLE", TypeOid.VARCHAR, getMaxNameLength()); String sql = "SELECT n.nspname,c.relname,u.usename,c.relacl,a.attname " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_attribute a " + " WHERE c.relnamespace = n.oid " + " AND u.usesysid = c.relowner " + " AND c.oid = a.attrelid " + " AND c.relkind = 'r' " + " AND a.attnum > 0 AND NOT a.attisdropped " + " AND " + resolveSchemaCondition( "n.nspname", schema); sql += " AND c.relname = '" + escapeQuotes(table) + "' "; if(columnNamePattern != null && !"".equals(columnNamePattern)) { sql += " AND a.attname LIKE '" + escapeQuotes(columnNamePattern) + "' "; } sql += " ORDER BY attname "; ResultSet rs = m_connection.createStatement().executeQuery(sql); String schemaName = null; String tableName = null; String column = null; String owner = null; String[] acls = null; HashMap permissions = null; String permNames[] = null; while(rs.next()) { schemaName = rs.getString("nspname"); tableName = rs.getString("relname"); column = rs.getString("attname"); owner = rs.getString("usename"); acls = (String[])rs.getObject("relacl"); permissions = parseACL(acls, owner); permNames = (String[])permissions.keySet().toArray(new String[permissions.size()]); sortStringArray(permNames); for(int i = 0; i < permNames.length; i++) { ArrayList grantees = (ArrayList)permissions.get(permNames[i]); for(int j = 0; j < grantees.size(); j++) { String grantee = (String)grantees.get(j); String grantable = owner.equals(grantee) ? "YES" : "NO"; Object[] tuple = new Object[8]; tuple[0] = null; tuple[1] = schemaName; tuple[2] = tableName; tuple[3] = column; tuple[4] = owner; tuple[5] = grantee; tuple[6] = permNames[i]; tuple[7] = grantable; v.add(tuple); } } } rs.close(); return createSyntheticResultSet(f, v); } /* * Get a description of the access rights for each table available in a * catalog. This method is currently unimplemented. <P>Only privileges * matching the schema and table name criteria are returned. They are * ordered by TABLE_SCHEM, TABLE_NAME, and PRIVILEGE. <P>Each privilige * description has the following columns: <OL> <LI><B>TABLE_CAT</B> String => * table catalog (may be null) <LI><B>TABLE_SCHEM</B> String => table * schema (may be null) <LI><B>TABLE_NAME</B> String => table name <LI><B>GRANTOR</B> => * grantor of access (may be null) <LI><B>GRANTEE</B> String => grantee of * access <LI><B>PRIVILEGE</B> String => name of access (SELECT, INSERT, * UPDATE, REFRENCES, ...) <LI><B>IS_GRANTABLE</B> String => "YES" if * grantee is permitted to grant to others; "NO" if not; null if unknown * </OL> @param catalog a catalog name; "" retrieves those without a catalog * @param schemaPattern a schema name pattern; "" retrieves those without a * schema @param tableNamePattern a table name pattern @return ResultSet * each row is a table privilege description * * @see #getSearchStringEscape */ public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { ResultSetField f[] = new ResultSetField[7]; ArrayList v = new ArrayList(); f[0] = new ResultSetField("TABLE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("TABLE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("TABLE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[3] = new ResultSetField("GRANTOR", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("GRANTEE", TypeOid.VARCHAR, getMaxNameLength()); f[5] = new ResultSetField("PRIVILEGE", TypeOid.VARCHAR, getMaxNameLength()); f[6] = new ResultSetField("IS_GRANTABLE", TypeOid.VARCHAR, getMaxNameLength()); String sql = "SELECT n.nspname,c.relname,u.usename,c.relacl " + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u " + " WHERE c.relnamespace = n.oid " + " AND u.usesysid = c.relowner " + " AND c.relkind = 'r' " + " AND " + resolveSchemaPatternCondition( "n.nspname", schemaPattern); if(tableNamePattern != null && !"".equals(tableNamePattern)) { sql += " AND c.relname LIKE '" + escapeQuotes(tableNamePattern) + "' "; } sql += " ORDER BY nspname, relname "; ResultSet rs = m_connection.createStatement().executeQuery(sql); String schema = null; String table = null; String owner = null; String[] acls = null; HashMap permissions = null; String permNames[] = null; while(rs.next()) { schema = rs.getString("nspname"); table = rs.getString("relname"); owner = rs.getString("usename"); acls = (String[])rs.getObject("relacl"); permissions = parseACL(acls, owner); permNames = (String[])permissions.keySet().toArray(new String[permissions.size()]); sortStringArray(permNames); for(int i = 0; i < permNames.length; i++) { ArrayList grantees = (ArrayList)permissions.get(permNames[i]); for(int j = 0; j < grantees.size(); j++) { String grantee = (String)grantees.get(j); String grantable = owner.equals(grantee) ? "YES" : "NO"; Object[] tuple = new Object[7]; tuple[0] = null; tuple[1] = schema; tuple[2] = table; tuple[3] = owner; tuple[4] = grantee; tuple[5] = permNames[i]; tuple[6] = grantable; v.add(tuple); } } } rs.close(); return createSyntheticResultSet(f, v); } private static void sortStringArray(String s[]) { for(int i = 0; i < s.length - 1; i++) { for(int j = i + 1; j < s.length; j++) { if(s[i].compareTo(s[j]) > 0) { String tmp = s[i]; s[i] = s[j]; s[j] = tmp; } } } } /** * Add the user described by the given acl to the ArrayLists of users with the * privileges described by the acl. */ private void addACLPrivileges(String acl, HashMap privileges) { int equalIndex = acl.lastIndexOf("="); String name = acl.substring(0, equalIndex); if(name.length() == 0) { name = "PUBLIC"; } String privs = acl.substring(equalIndex + 1); for(int i = 0; i < privs.length(); i++) { char c = privs.charAt(i); String sqlpriv; switch(c) { case 'a': sqlpriv = "INSERT"; break; case 'r': sqlpriv = "SELECT"; break; case 'w': sqlpriv = "UPDATE"; break; case 'd': sqlpriv = "DELETE"; break; case 'R': sqlpriv = "RULE"; break; case 'x': sqlpriv = "REFERENCES"; break; case 't': sqlpriv = "TRIGGER"; break; // the folloowing can't be granted to a table, but // we'll keep them for completeness. case 'X': sqlpriv = "EXECUTE"; break; case 'U': sqlpriv = "USAGE"; break; case 'C': sqlpriv = "CREATE"; break; case 'T': sqlpriv = "CREATE TEMP"; break; default: sqlpriv = "UNKNOWN"; } ArrayList usersWithPermission = (ArrayList)privileges.get(sqlpriv); if(usersWithPermission == null) { usersWithPermission = new ArrayList(); privileges.put(sqlpriv, usersWithPermission); } usersWithPermission.add(name); } } /** * Take the a String representing an array of ACLs and return a HashMap * mapping the SQL permission name to a ArrayList of usernames who have that * permission. */ protected HashMap parseACL(String[] aclArray, String owner) { if(aclArray == null || aclArray.length == 0) { // null acl is a shortcut for owner having full privs aclArray = new String[] { owner + "=arwdRxt" }; } HashMap privileges = new HashMap(); for(int i = 0; i < aclArray.length; i++) { String acl = aclArray[i]; addACLPrivileges(acl, privileges); } return privileges; } /* * Get a description of a table's optimal set of columns that uniquely * identifies a row. They are ordered by SCOPE. <P>Each column description * has the following columns: <OL> <LI><B>SCOPE</B> short => actual scope * of result <UL> <LI> bestRowTemporary - very temporary, while using row * <LI> bestRowTransaction - valid for remainder of current transaction <LI> * bestRowSession - valid for remainder of current session </UL> <LI><B>COLUMN_NAME</B> * String => column name <LI><B>DATA_TYPE</B> short => SQL data type from * java.sql.Types <LI><B>TYPE_NAME</B> String => Data source dependent * type name <LI><B>COLUMN_SIZE</B> int => precision <LI><B>BUFFER_LENGTH</B> * int => not used <LI><B>DECIMAL_DIGITS</B> short => scale <LI><B>PSEUDO_COLUMN</B> * short => is this a pseudo column like an Oracle ROWID <UL> <LI> * bestRowUnknown - may or may not be pseudo column <LI> bestRowNotPseudo - * is NOT a pseudo column <LI> bestRowPseudo - is a pseudo column </UL> * </OL> @param catalog a catalog name; "" retrieves those without a catalog * @param schema a schema name; "" retrieves those without a schema @param * table a table name @param scope the scope of interest; use same values as * SCOPE @param nullable include columns that are nullable? @return * ResultSet each row is a column description */ // Implementation note: This is required for Borland's JBuilder to work public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { ResultSetField f[] = new ResultSetField[8]; ArrayList v = new ArrayList(); // The new ResultSet tuple stuff f[0] = new ResultSetField("SCOPE", TypeOid.INT2, 2); f[1] = new ResultSetField("COLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("DATA_TYPE", TypeOid.INT2, 2); f[3] = new ResultSetField("TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("COLUMN_SIZE", TypeOid.INT4, 4); f[5] = new ResultSetField("BUFFER_LENGTH", TypeOid.INT4, 4); f[6] = new ResultSetField("DECIMAL_DIGITS", TypeOid.INT2, 2); f[7] = new ResultSetField("PSEUDO_COLUMN", TypeOid.INT2, 2); /* * At the moment this simply returns a table's primary key, if there is * one. I believe other unique indexes, ctid, and oid should also be * considered. -KJ */ String where = ""; String 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_index i "; where = " AND ct.relnamespace = n.oid " + " AND " + resolveSchemaCondition( "n.nspname", schema); String sql = "SELECT a.attname, a.atttypid as atttypid " + from + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid " + " AND a.attrelid=ci.oid AND i.indisprimary " + " AND ct.relname = '" + escapeQuotes(table) + "' " + where + " ORDER BY a.attnum "; ResultSet rs = m_connection.createStatement().executeQuery(sql); while(rs.next()) { Object[] tuple = new Object[8]; Oid columnTypeOid = (Oid)rs.getObject("atttypid"); tuple[0] = new Short((short)scope); tuple[1] = rs.getString("attname"); tuple[2] = new Short((short)m_connection.getSQLType(columnTypeOid)); tuple[3] = m_connection.getPGType(columnTypeOid); tuple[4] = null; tuple[5] = null; tuple[6] = null; tuple[7] = new Short((short)java.sql.DatabaseMetaData.bestRowNotPseudo); v.add(tuple); } return createSyntheticResultSet(f, v); } /* * Get a description of a table's columns that are automatically updated * when any value in a row is updated. They are unordered. <P>Each column * description has the following columns: <OL> <LI><B>SCOPE</B> short => * is not used <LI><B>COLUMN_NAME</B> String => column name <LI><B>DATA_TYPE</B> * short => SQL data type from java.sql.Types <LI><B>TYPE_NAME</B> String => * Data source dependent type name <LI><B>COLUMN_SIZE</B> int => precision * <LI><B>BUFFER_LENGTH</B> int => length of column value in bytes <LI><B>DECIMAL_DIGITS</B> * short => scale <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo * column like an Oracle ROWID <UL> <LI> versionColumnUnknown - may or may * not be pseudo column <LI> versionColumnNotPseudo - is NOT a pseudo column * <LI> versionColumnPseudo - is a pseudo column </UL> </OL> @param catalog * a catalog name; "" retrieves those without a catalog @param schema a * schema name; "" retrieves those without a schema @param table a table * name @return ResultSet each row is a column description */ public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { ResultSetField f[] = new ResultSetField[8]; ArrayList v = new ArrayList(); // The new ResultSet tuple stuff f[0] = new ResultSetField("SCOPE", TypeOid.INT2, 2); f[1] = new ResultSetField("COLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("DATA_TYPE", TypeOid.INT2, 2); f[3] = new ResultSetField("TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("COLUMN_SIZE", TypeOid.INT4, 4); f[5] = new ResultSetField("BUFFER_LENGTH", TypeOid.INT4, 4); f[6] = new ResultSetField("DECIMAL_DIGITS", TypeOid.INT2, 2); f[7] = new ResultSetField("PSEUDO_COLUMN", TypeOid.INT2, 2); Object[] tuple = new Object[8]; /* * Postgresql does not have any column types that are automatically * updated like some databases' timestamp type. We can't tell what rules * or triggers might be doing, so we are left with the system columns * that change on an update. An update may change all of the following * system columns: ctid, xmax, xmin, cmax, and cmin. Depending on if we * are in a transaction and wether we roll it back or not the only * guaranteed change is to ctid. -KJ */ tuple[0] = null; tuple[1] = "ctid"; tuple[2] = new Short((short)m_connection.getSQLType("tid")); tuple[3] = "tid"; tuple[4] = null; tuple[5] = null; tuple[6] = null; tuple[7] = new Short((short)java.sql.DatabaseMetaData.versionColumnPseudo); v.add(tuple); /* * Perhaps we should check that the given catalog.schema.table actually * exists. -KJ */ return createSyntheticResultSet(f, v); } /* * Get a description of a table's primary key columns. They are ordered by * COLUMN_NAME. <P>Each column description has the following columns: <OL> * <LI><B>TABLE_CAT</B> String => table catalog (may be null) <LI><B>TABLE_SCHEM</B> * String => table schema (may be null) <LI><B>TABLE_NAME</B> String => * table name <LI><B>COLUMN_NAME</B> String => column name <LI><B>KEY_SEQ</B> * short => sequence number within primary key <LI><B>PK_NAME</B> String => * primary key name (may be null) </OL> @param catalog a catalog name; "" * retrieves those without a catalog @param schema a schema name pattern; "" * retrieves those without a schema @param table a table name @return * ResultSet each row is a primary key column description */ public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { String from; String where = ""; String select = "SELECT NULL 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_index i "; where = " AND ct.relnamespace = n.oid AND " + resolveSchemaCondition("n.nspname", schema); String sql = select + " ct.relname AS TABLE_NAME, " + " a.attname AS COLUMN_NAME, " + " a.attnum::int2 AS KEY_SEQ, " + " ci.relname AS PK_NAME " + from + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid " + " AND a.attrelid=ci.oid AND i.indisprimary "; if(table != null && !"".equals(table)) { sql += " AND ct.relname = '" + escapeQuotes(table) + "' "; } sql += where + " ORDER BY table_name, pk_name, key_seq"; return createMetaDataStatement().executeQuery(sql); } /** * @param primaryCatalog * @param primarySchema * @param primaryTable if provided will get the keys exported by this table * @param foreignTable if provided will get the keys imported by this table * @return ResultSet * @throws SQLException */ protected java.sql.ResultSet getImportedExportedKeys(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { ResultSetField f[] = new ResultSetField[14]; f[0] = new ResultSetField("PKTABLE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("PKTABLE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[2] = new ResultSetField("PKTABLE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[3] = new ResultSetField("PKCOLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("FKTABLE_CAT", TypeOid.VARCHAR, getMaxNameLength()); f[5] = new ResultSetField("FKTABLE_SCHEM", TypeOid.VARCHAR, getMaxNameLength()); f[6] = new ResultSetField("FKTABLE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[7] = new ResultSetField("FKCOLUMN_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[8] = new ResultSetField("KEY_SEQ", TypeOid.INT2, 2); f[9] = new ResultSetField("UPDATE_RULE", TypeOid.INT2, 2); f[10] = new ResultSetField("DELETE_RULE", TypeOid.INT2, 2); f[11] = new ResultSetField("FK_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[12] = new ResultSetField("PK_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[13] = new ResultSetField("DEFERRABILITY", TypeOid.INT2, 2); /* * The addition of the pg_constraint in 7.3 table should have really * helped us out here, but it comes up just a bit short. - The conkey, * confkey columns aren't really useful without contrib/array unless we * want to issues separate queries. - Unique indexes that can support * foreign keys are not necessarily added to pg_constraint. Also * multiple unique indexes covering the same keys can be created which * make it difficult to determine the PK_NAME field. */ 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, " + "pos.n::int2 AS KEY_SEQ, " + "CASE con.confupdtype " + " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + " ELSE NULL END::int2 AS UPDATE_RULE, " + "CASE con.confdeltype " + " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + " ELSE NULL END::int2 AS DELETE_RULE, " + "con.conname AS FK_NAME, pkic.relname AS PK_NAME, " + "CASE " + " WHEN con.condeferrable AND con.condeferred THEN " + DatabaseMetaData.importedKeyInitiallyDeferred + " WHEN con.condeferrable THEN " + DatabaseMetaData.importedKeyInitiallyImmediate + " ELSE " + DatabaseMetaData.importedKeyNotDeferrable + " END::int2 AS DEFERRABILITY " + " FROM " + " pg_catalog.pg_namespace pkn, pg_catalog.pg_class pkc, pg_catalog.pg_attribute pka, " + " pg_catalog.pg_namespace fkn, pg_catalog.pg_class fkc, pg_catalog.pg_attribute fka, " + " pg_catalog.pg_constraint con, " + " pg_catalog.generate_series(1, " + getMaxIndexKeys() + ") pos(n), " + " pg_catalog.pg_depend dep, pg_catalog.pg_class pkic " + " WHERE pkn.oid = pkc.relnamespace AND pkc.oid = pka.attrelid AND pka.attnum = con.confkey[pos.n] AND con.confrelid = pkc.oid " + " AND fkn.oid = fkc.relnamespace AND fkc.oid = fka.attrelid AND fka.attnum = con.conkey[pos.n] AND con.conrelid = fkc.oid " + " AND con.contype = 'f' AND con.oid = dep.objid AND pkic.oid = dep.refobjid AND pkic.relkind = 'i' AND dep.classid = 'pg_constraint'::regclass::oid AND dep.refclassid = 'pg_class'::regclass::oid " + " AND " + resolveSchemaCondition("pkn.nspname", primarySchema) + " AND " + resolveSchemaCondition("fkn.nspname", foreignSchema); if(primaryTable != null && !"".equals(primaryTable)) { sql += " AND pkc.relname = '" + escapeQuotes(primaryTable) + "' "; } if(foreignTable != null && !"".equals(foreignTable)) { sql += " AND fkc.relname = '" + escapeQuotes(foreignTable) + "' "; } if(primaryTable != null) { sql += " ORDER BY fkn.nspname,fkc.relname,pos.n"; } else { sql += " ORDER BY pkn.nspname,pkc.relname,pos.n"; } return createMetaDataStatement().executeQuery(sql); } /* * Get a description of the primary key columns that are referenced by a * table's foreign key columns (the primary keys imported by a table). They * are ordered by PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. <P>Each * primary key column description has the following columns: <OL> <LI><B>PKTABLE_CAT</B> * String => primary key table catalog being imported (may be null) <LI><B>PKTABLE_SCHEM</B> * String => primary key table schema being imported (may be null) <LI><B>PKTABLE_NAME</B> * String => primary key table name being imported <LI><B>PKCOLUMN_NAME</B> * String => primary key column name being imported <LI><B>FKTABLE_CAT</B> * String => foreign key table catalog (may be null) <LI><B>FKTABLE_SCHEM</B> * String => foreign key table schema (may be null) <LI><B>FKTABLE_NAME</B> * String => foreign key table name <LI><B>FKCOLUMN_NAME</B> String => * foreign key column name <LI><B>KEY_SEQ</B> short => sequence number * within foreign key <LI><B>UPDATE_RULE</B> short => What happens to * foreign key when primary is updated: <UL> <LI> importedKeyCascade - * change imported key to agree with primary key update <LI> * importedKeyRestrict - do not allow update of primary key if it has been * imported <LI> importedKeySetNull - change imported key to NULL if its * primary key has been updated </UL> <LI><B>DELETE_RULE</B> short => What * happens to the foreign key when primary is deleted. <UL> <LI> * importedKeyCascade - delete rows that import a deleted key <LI> * importedKeyRestrict - do not allow delete of primary key if it has been * imported <LI> importedKeySetNull - change imported key to NULL if its * primary key has been deleted </UL> <LI><B>FK_NAME</B> String => foreign * key name (may be null) <LI><B>PK_NAME</B> String => primary key name * (may be null) </OL> @param catalog a catalog name; "" retrieves those * without a catalog @param schema a schema name pattern; "" retrieves those * without a schema @param table a table name @return ResultSet each row is * a primary key column description * * @see #getExportedKeys */ public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { return getImportedExportedKeys(null, null, null, catalog, schema, table); } /* * Get a description of a foreign key columns that reference a table's * primary key columns (the foreign keys exported by a table). They are * ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and KEY_SEQ. This * method is currently unimplemented. <P>Each foreign key column * description has the following columns: <OL> <LI><B>PKTABLE_CAT</B> * String => primary key table catalog (may be null) <LI><B>PKTABLE_SCHEM</B> * String => primary key table schema (may be null) <LI><B>PKTABLE_NAME</B> * String => primary key table name <LI><B>PKCOLUMN_NAME</B> String => * primary key column name <LI><B>FKTABLE_CAT</B> String => foreign key * table catalog (may be null) being exported (may be null) <LI><B>FKTABLE_SCHEM</B> * String => foreign key table schema (may be null) being exported (may be * null) <LI><B>FKTABLE_NAME</B> String => foreign key table name being * exported <LI><B>FKCOLUMN_NAME</B> String => foreign key column name * being exported <LI><B>KEY_SEQ</B> short => sequence number within * foreign key <LI><B>UPDATE_RULE</B> short => What happens to foreign key * when primary is updated: <UL> <LI> importedKeyCascade - change imported * key to agree with primary key update <LI> importedKeyRestrict - do not * allow update of primary key if it has been imported <LI> * importedKeySetNull - change imported key to NULL if its primary key has * been updated </UL> <LI><B>DELETE_RULE</B> short => What happens to the * foreign key when primary is deleted. <UL> <LI> importedKeyCascade - * delete rows that import a deleted key <LI> importedKeyRestrict - do not * allow delete of primary key if it has been imported <LI> * importedKeySetNull - change imported key to NULL if its primary key has * been deleted </UL> <LI><B>FK_NAME</B> String => foreign key identifier * (may be null) <LI><B>PK_NAME</B> String => primary key identifier (may * be null) </OL> @param catalog a catalog name; "" retrieves those without * a catalog @param schema a schema name pattern; "" retrieves those without * a schema @param table a table name @return ResultSet each row is a * foreign key column description * * @see #getImportedKeys */ public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { return getImportedExportedKeys(catalog, schema, table, null, null, null); } /* * Get a description of the foreign key columns in the foreign key table * that reference the primary key columns of the primary key table (describe * how one table imports another's key.) This should normally return a * single foreign key/primary key pair (most tables only import a foreign * key from a table once.) They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, * FKTABLE_NAME, and KEY_SEQ. This method is currently unimplemented. <P>Each * foreign key column description has the following columns: <OL> <LI><B>PKTABLE_CAT</B> * String => primary key table catalog (may be null) <LI><B>PKTABLE_SCHEM</B> * String => primary key table schema (may be null) <LI><B>PKTABLE_NAME</B> * String => primary key table name <LI><B>PKCOLUMN_NAME</B> String => * primary key column name <LI><B>FKTABLE_CAT</B> String => foreign key * table catalog (may be null) being exported (may be null) <LI><B>FKTABLE_SCHEM</B> * String => foreign key table schema (may be null) being exported (may be * null) <LI><B>FKTABLE_NAME</B> String => foreign key table name being * exported <LI><B>FKCOLUMN_NAME</B> String => foreign key column name * being exported <LI><B>KEY_SEQ</B> short => sequence number within * foreign key <LI><B>UPDATE_RULE</B> short => What happens to foreign key * when primary is updated: <UL> <LI> importedKeyCascade - change imported * key to agree with primary key update <LI> importedKeyRestrict - do not * allow update of primary key if it has been imported <LI> * importedKeySetNull - change imported key to NULL if its primary key has * been updated </UL> <LI><B>DELETE_RULE</B> short => What happens to the * foreign key when primary is deleted. <UL> <LI> importedKeyCascade - * delete rows that import a deleted key <LI> importedKeyRestrict - do not * allow delete of primary key if it has been imported <LI> * importedKeySetNull - change imported key to NULL if its primary key has * been deleted </UL> <LI><B>FK_NAME</B> String => foreign key identifier * (may be null) <LI><B>PK_NAME</B> String => primary key identifier (may * be null) </OL> @param catalog a catalog name; "" retrieves those without * a catalog @param schema a schema name pattern; "" retrieves those without * a schema @param table a table name @return ResultSet each row is a * foreign key column description * * @see #getImportedKeys */ public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { return getImportedExportedKeys(primaryCatalog, primarySchema, primaryTable, foreignCatalog, foreignSchema, foreignTable); } /* * Get a description of all the standard SQL types supported by this * database. They are ordered by DATA_TYPE and then by how closely the data * type maps to the corresponding JDBC SQL type. <P>Each type description * has the following columns: <OL> <LI><B>TYPE_NAME</B> String => Type * name <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types * <LI><B>PRECISION</B> int => maximum precision <LI><B>LITERAL_PREFIX</B> * String => prefix used to quote a literal (may be null) <LI><B>LITERAL_SUFFIX</B> * String => suffix used to quote a literal (may be null) <LI><B>CREATE_PARAMS</B> * String => parameters used in creating the type (may be null) <LI><B>NULLABLE</B> * short => can you use NULL for this type? <UL> <LI> typeNoNulls - does not * allow NULL values <LI> typeNullable - allows NULL values <LI> * typeNullableUnknown - nullability unknown </UL> <LI><B>CASE_SENSITIVE</B> * boolean=> is it case sensitive? <LI><B>SEARCHABLE</B> short => can you * use "WHERE" based on this type: <UL> <LI> typePredNone - No support <LI> * typePredChar - Only supported with WHERE .. LIKE <LI> typePredBasic - * Supported except for WHERE .. LIKE <LI> typeSearchable - Supported for * all WHERE .. </UL> <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it * unsigned? <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money * value? <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an * auto-increment value? <LI><B>LOCAL_TYPE_NAME</B> String => localized * version of type name (may be null) <LI><B>MINIMUM_SCALE</B> short => * minimum scale supported <LI><B>MAXIMUM_SCALE</B> short => maximum scale * supported <LI><B>SQL_DATA_TYPE</B> int => unused <LI><B>SQL_DATETIME_SUB</B> * int => unused <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10 </OL> * @return ResultSet each row is a SQL type description */ public java.sql.ResultSet getTypeInfo() throws SQLException { ResultSetField f[] = new ResultSetField[18]; ArrayList v = new ArrayList(); // The new ResultSet tuple stuff f[0] = new ResultSetField("TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[1] = new ResultSetField("DATA_TYPE", TypeOid.INT2, 2); f[2] = new ResultSetField("PRECISION", TypeOid.INT4, 4); f[3] = new ResultSetField("LITERAL_PREFIX", TypeOid.VARCHAR, getMaxNameLength()); f[4] = new ResultSetField("LITERAL_SUFFIX", TypeOid.VARCHAR, getMaxNameLength()); f[5] = new ResultSetField("CREATE_PARAMS", TypeOid.VARCHAR, getMaxNameLength()); f[6] = new ResultSetField("NULLABLE", TypeOid.INT2, 2); f[7] = new ResultSetField("CASE_SENSITIVE", TypeOid.BOOL, 1); f[8] = new ResultSetField("SEARCHABLE", TypeOid.INT2, 2); f[9] = new ResultSetField("UNSIGNED_ATTRIBUTE", TypeOid.BOOL, 1); f[10] = new ResultSetField("FIXED_PREC_SCALE", TypeOid.BOOL, 1); f[11] = new ResultSetField("AUTO_INCREMENT", TypeOid.BOOL, 1); f[12] = new ResultSetField("LOCAL_TYPE_NAME", TypeOid.VARCHAR, getMaxNameLength()); f[13] = new ResultSetField("MINIMUM_SCALE", TypeOid.INT2, 2); f[14] = new ResultSetField("MAXIMUM_SCALE", TypeOid.INT2, 2); f[15] = new ResultSetField("SQL_DATA_TYPE", TypeOid.INT4, 4); f[16] = new ResultSetField("SQL_DATETIME_SUB", TypeOid.INT4, 4); f[17] = new ResultSetField("NUM_PREC_RADIX", TypeOid.INT4, 4); String sql = "SELECT typname FROM pg_catalog.pg_type where typrelid = 0"; ResultSet rs = m_connection.createStatement().executeQuery(sql); // cache some results, this will keep memory useage down, and speed // things up a little. Integer i9 = new Integer(9); Integer i10 = new Integer(10); Short nn = new Short((short)java.sql.DatabaseMetaData.typeNoNulls); Short ts = new Short((short)java.sql.DatabaseMetaData.typeSearchable); String typname = null; while(rs.next()) { Object[] tuple = new Object[18]; typname = rs.getString(1); tuple[0] = typname; tuple[1] = new Short((short)m_connection.getSQLType(typname)); tuple[2] = i9; // for now tuple[6] = nn; // for now tuple[7] = Boolean.FALSE; // false for now - not case sensitive tuple[8] = ts; tuple[9] = Boolean.FALSE; // false for now - it's signed tuple[10] = Boolean.FALSE; // false for now - must handle money tuple[11] = Boolean.FALSE; // false - it isn't autoincrement // 12 - LOCAL_TYPE_NAME is null // 13 & 14 ? // 15 & 16 are unused so we return null tuple[17] = i10; // everything is base 10 v.add(tuple); // add pseudo-type serial, bigserial if(typname.equals("int4")) { Object[] tuple1 = (Object[])tuple.clone(); tuple1[0] = "serial"; tuple1[11] = Boolean.TRUE; v.add(tuple1); } else if(typname.equals("int8")) { Object[] tuple1 = (Object[])tuple.clone(); tuple1[0] = "bigserial"; tuple1[11] = Boolean.TRUE; v.add(tuple1); } } rs.close(); return createSyntheticResultSet(f, v); } /* * Get a description of a table's indices and statistics. They are ordered * by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. <P>Each index * column description has the following columns: <OL> <LI><B>TABLE_CAT</B> * String => table catalog (may be null) <LI><B>TABLE_SCHEM</B> String => * table schema (may be null) <LI><B>TABLE_NAME</B> String => table name * <LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique? false * when TYPE is tableIndexStatistic <LI><B>INDEX_QUALIFIER</B> String => * index catalog (may be null); null when TYPE is tableIndexStatistic <LI><B>INDEX_NAME</B> * String => index name; null when TYPE is tableIndexStatistic <LI><B>TYPE</B> * short => index type: <UL> <LI> tableIndexStatistic - this identifies * table statistics that are returned in conjuction with a table's index * descriptions <LI> tableIndexClustered - this is a clustered index <LI> * tableIndexHashed - this is a hashed index <LI> tableIndexOther - this is * some other style of index </UL> <LI><B>ORDINAL_POSITION</B> short => * column sequence number within index; zero when TYPE is * tableIndexStatistic <LI><B>COLUMN_NAME</B> String => column name; null * when TYPE is tableIndexStatistic <LI><B>ASC_OR_DESC</B> String => * column sort sequence, "A" => ascending "D" => descending, may be null if * sort sequence is not supported; null when TYPE is tableIndexStatistic * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then this * is the number of rows in the table; otherwise it is the number of unique * values in the index. <LI><B>PAGES</B> int => When TYPE is * tableIndexStatisic then this is the number of pages used for the table, * otherwise it is the number of pages used for the current index. <LI><B>FILTER_CONDITION</B> * String => Filter condition, if any. (may be null) </OL> @param catalog a * catalog name; "" retrieves those without a catalog @param schema a schema * name pattern; "" retrieves those without a schema @param table a table * name @param unique when true, return only indices for unique values; when * false, return indices regardless of whether unique or not @param * approximate when true, result is allowed to reflect approximate or out of * data values; when false, results are requested to be accurate @return * ResultSet each row is an index column description */ // Implementation note: This is required for Borland's JBuilder to work public java.sql.ResultSet getIndexInfo(String catalog, String schema, String tableName, boolean unique, boolean approximate) throws SQLException { String select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "; String from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_index i, pg_catalog.pg_attribute a, pg_catalog.pg_am am "; String where = " AND n.oid = ct.relnamespace " + " AND " + resolveSchemaCondition("n.nspname", schema); String sql = select + " ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, " + " CASE i.indisclustered " + " WHEN true THEN " + java.sql.DatabaseMetaData.tableIndexClustered + " ELSE CASE am.amname " + " WHEN 'hash' THEN " + java.sql.DatabaseMetaData.tableIndexHashed + " ELSE " + java.sql.DatabaseMetaData.tableIndexOther + " END " + " END::int2 AS TYPE, " + " a.attnum::int2 AS ORDINAL_POSITION, " + " a.attname AS COLUMN_NAME, " + " NULL AS ASC_OR_DESC, " + " ci.reltuples AS CARDINALITY, " + " ci.relpages AS PAGES, " + " NULL AS FILTER_CONDITION " + from + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid AND a.attrelid=ci.oid AND ci.relam=am.oid " + where + " AND ct.relname = '" + escapeQuotes(tableName) + "' "; if(unique) { sql += " AND i.indisunique "; } sql += " ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION "; return createMetaDataStatement().executeQuery(sql); } // ** JDBC 2 Extensions ** /* * Does the database support the given result set type? @param type - * defined in java.sql.ResultSet @return true if so; false otherwise * @exception SQLException - if a database access error occurs */ public boolean supportsResultSetType(int type) throws SQLException { // The only type we support return type == java.sql.ResultSet.TYPE_FORWARD_ONLY; } /* * Does the database support the concurrency type in combination with the * given result set type? @param type - defined in java.sql.ResultSet @param * concurrency - type defined in java.sql.ResultSet @return true if so; * false otherwise @exception SQLException - if a database access error * occurs */ public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { // These combinations are not supported! if(type != java.sql.ResultSet.TYPE_FORWARD_ONLY) return false; // We support only Concur Read Only if(concurrency != java.sql.ResultSet.CONCUR_READ_ONLY) return false; // Everything else we do return true; } /* lots of unsupported stuff... */ public boolean ownUpdatesAreVisible(int type) throws SQLException { return true; } public boolean ownDeletesAreVisible(int type) throws SQLException { return true; } public boolean ownInsertsAreVisible(int type) throws SQLException { // indicates that return true; } public boolean othersUpdatesAreVisible(int type) throws SQLException { return false; } public boolean othersDeletesAreVisible(int i) throws SQLException { return false; } public boolean othersInsertsAreVisible(int type) throws SQLException { return false; } public boolean updatesAreDetected(int type) throws SQLException { return false; } public boolean deletesAreDetected(int i) throws SQLException { return false; } public boolean insertsAreDetected(int type) throws SQLException { return false; } /* * Indicates whether the driver supports batch updates. */ public boolean supportsBatchUpdates() throws SQLException { return true; } /** * @param catalog String * @param schemaPattern String * @param typeNamePattern String * @param types int[] * @throws SQLException * @return ResultSet */ public java.sql.ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { String sql = "select " + "null as type_cat, n.nspname as type_schem, t.typname as type_name, null as class_name, " + "CASE WHEN t.typtype='c' then " + java.sql.Types.STRUCT + " else " + java.sql.Types.DISTINCT + " end as data_type, pg_catalog.obj_description(t.oid, 'pg_type') " + "as remarks, CASE WHEN t.typtype = 'd' then (select CASE"; for(int i = 0; i < SPIConnection.JDBC3_TYPE_NAMES.length; i++) { sql += " when typname = '" + SPIConnection.JDBC_TYPE_NUMBERS[i] + "' then " + SPIConnection.JDBC_TYPE_NUMBERS[i]; } sql += " else " + java.sql.Types.OTHER + " end from pg_type where oid=t.typbasetype) " + "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'"; String toAdd = ""; if(types != null) { toAdd += " and (false "; for(int i = 0; i < types.length; i++) { switch(types[i]) { case java.sql.Types.STRUCT: toAdd += " or t.typtype = 'c'"; break; case java.sql.Types.DISTINCT: toAdd += " or t.typtype = 'd'"; break; } } toAdd += " ) "; } else { toAdd += " and t.typtype IN ('c','d') "; } // spec says that if typeNamePattern is a fully qualified name // then the schema and catalog are ignored if(typeNamePattern != null) { // search for qualifier int firstQualifier = typeNamePattern.indexOf('.'); int secondQualifier = typeNamePattern.lastIndexOf('.'); if(firstQualifier != -1) // if one of them is -1 they both will // be { if(firstQualifier != secondQualifier) { // we have a catalog.schema.typename, ignore catalog schemaPattern = typeNamePattern.substring( firstQualifier + 1, secondQualifier); } else { // we just have a schema.typename schemaPattern = typeNamePattern .substring(0, firstQualifier); } // strip out just the typeName typeNamePattern = typeNamePattern .substring(secondQualifier + 1); } toAdd += " and t.typname like '" + escapeQuotes(typeNamePattern) + "'"; } // schemaPattern may have been modified above if(schemaPattern != null) { toAdd += " and n.nspname like '" + escapeQuotes(schemaPattern) + "'"; } sql += toAdd; sql += " order by data_type, type_schem, type_name"; java.sql.ResultSet rs = createMetaDataStatement().executeQuery(sql); return rs; } /* * Retrieves the connection that produced this metadata object. @return the * connection that produced this metadata object */ public Connection getConnection() throws SQLException { return m_connection; } /* I don't find these in the spec!?! */ public boolean rowChangesAreDetected(int type) throws SQLException { return false; } public boolean rowChangesAreVisible(int type) throws SQLException { return false; } private Statement createMetaDataStatement() throws SQLException { return m_connection.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); } /** * Retrieves whether this database supports savepoints. * * @return <code>true</code> if savepoints are supported; * <code>false</code> otherwise * @exception SQLException if a database access error occurs * @since 1.4 */ public boolean supportsSavepoints() throws SQLException { return this.getDatabaseMajorVersion() >= 8; } /** * Retrieves whether this database supports named parameters to callable * statements. * * @return <code>true</code> if named parameters are supported; * <code>false</code> otherwise * @exception SQLException if a database access error occurs * @since 1.4 */ public boolean supportsNamedParameters() throws SQLException { return false; } /** * Retrieves whether it is possible to have multiple <code>ResultSet</code> * objects returned from a <code>CallableStatement</code> object * simultaneously. * * @return <code>true</code> if a <code>CallableStatement</code> object * can return multiple <code>ResultSet</code> objects * simultaneously; <code>false</code> otherwise * @exception SQLException if a datanase access error occurs * @since 1.4 */ public boolean supportsMultipleOpenResults() throws SQLException { return false; } /** * Retrieves whether auto-generated keys can be retrieved after a statement * has been executed. * * @return <code>true</code> if auto-generated keys can be retrieved after * a statement has executed; <code>false</code> otherwise * @exception SQLException if a database access error occurs * @since 1.4 */ public boolean supportsGetGeneratedKeys() throws SQLException { return false; } /** * Retrieves a description of the user-defined type (UDT) hierarchies * defined in a particular schema in this database. Only the immediate super * type/ sub type relationship is modeled. * <P> * Only supertype information for UDTs matching the catalog, schema, and * type name is returned. The type name parameter may be a fully-qualified * name. When the UDT name supplied is a fully-qualified name, the catalog * and schemaPattern parameters are ignored. * <P> * If a UDT does not have a direct super type, it is not listed here. A row * of the <code>ResultSet</code> object returned by this method describes * the designated UDT and a direct supertype. A row has the following * columns: * <OL> * <LI><B>TYPE_CAT</B> String => the UDT's catalog (may be * <code>null</code>) * <LI><B>TYPE_SCHEM</B> String => UDT's schema (may be * <code>null</code>) * <LI><B>TYPE_NAME</B> String => type name of the UDT * <LI><B>SUPERTYPE_CAT</B> String => the direct super type's catalog * (may be <code>null</code>) * <LI><B>SUPERTYPE_SCHEM</B> String => the direct super type's schema * (may be <code>null</code>) * <LI><B>SUPERTYPE_NAME</B> String => the direct super type's name * </OL> * <P> * <B>Note:</B> If the driver does not support type hierarchies, an empty * result set is returned. * * @param catalog a catalog name; "" retrieves those without a catalog; * <code>null</code> means drop catalog name from the selection * criteria * @param schemaPattern a schema name pattern; "" retrieves those without a * schema * @param typeNamePattern a UDT name pattern; may be a fully-qualified name * @return a <code>ResultSet</code> object in which a row gives * information about the designated UDT * @throws SQLException if a database access error occurs * @since 1.4 */ public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { throw new UnsupportedFeatureException("DatabaseMetaData.getSuperTypes"); } /** * Retrieves a description of the table hierarchies defined in a particular * schema in this database. * <P> * Only supertable information for tables matching the catalog, schema and * table name are returned. The table name parameter may be a fully- * qualified name, in which case, the catalog and schemaPattern parameters * are ignored. If a table does not have a super table, it is not listed * here. Supertables have to be defined in the same catalog and schema as * the sub tables. Therefore, the type description does not need to include * this information for the supertable. * <P> * Each type description has the following columns: * <OL> * <LI><B>TABLE_CAT</B> String => the type's catalog (may be * <code>null</code>) * <LI><B>TABLE_SCHEM</B> String => type's schema (may be * <code>null</code>) * <LI><B>TABLE_NAME</B> String => type name * <LI><B>SUPERTABLE_NAME</B> String => the direct super type's name * </OL> * <P> * <B>Note:</B> If the driver does not support type hierarchies, an empty * result set is returned. * * @param catalog a catalog name; "" retrieves those without a catalog; * <code>null</code> means drop catalog name from the selection * criteria * @param schemaPattern a schema name pattern; "" retrieves those without a * schema * @param tableNamePattern a table name pattern; may be a fully-qualified * name * @return a <code>ResultSet</code> object in which each row is a type * description * @throws SQLException if a database access error occurs * @since 1.4 */ public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { throw new UnsupportedFeatureException("DatabaseMetaData.getSuperTables"); } /** * Retrieves a description of the given attribute of the given type for a * user-defined type (UDT) that is available in the given schema and * catalog. * <P> * Descriptions are returned only for attributes of UDTs matching the * catalog, schema, type, and attribute name criteria. They are ordered by * TYPE_SCHEM, TYPE_NAME and ORDINAL_POSITION. This description does not * contain inherited attributes. * <P> * The <code>ResultSet</code> object that is returned has the following * columns: * <OL> * <LI><B>TYPE_CAT</B> String => type catalog (may be <code>null</code>) * <LI><B>TYPE_SCHEM</B> String => type schema (may be <code>null</code>) * <LI><B>TYPE_NAME</B> String => type name * <LI><B>ATTR_NAME</B> String => attribute name * <LI><B>DATA_TYPE</B> short => attribute type SQL type from * java.sql.Types * <LI><B>ATTR_TYPE_NAME</B> String => Data source dependent type name. * For a UDT, the type name is fully qualified. For a REF, the type name is * fully qualified and represents the target type of the reference type. * <LI><B>ATTR_SIZE</B> int => column size. For char or date types this * is the maximum number of characters; for numeric or decimal types this is * precision. * <LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits * <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2) * <LI><B>NULLABLE</B> int => whether NULL is allowed * <UL> * <LI> attributeNoNulls - might not allow NULL values * <LI> attributeNullable - definitely allows NULL values * <LI> attributeNullableUnknown - nullability unknown * </UL> * <LI><B>REMARKS</B> String => comment describing column (may be * <code>null</code>) * <LI><B>ATTR_DEF</B> String => default value (may be <code>null</code>) * <LI><B>SQL_DATA_TYPE</B> int => unused * <LI><B>SQL_DATETIME_SUB</B> int => unused * <LI><B>CHAR_OCTET_LENGTH</B> int => for char types the maximum number * of bytes in the column * <LI><B>ORDINAL_POSITION</B> int => index of column in table (starting * at 1) * <LI><B>IS_NULLABLE</B> String => "NO" means column definitely does not * allow NULL values; "YES" means the column might allow NULL values. An * empty string means unknown. * <LI><B>SCOPE_CATALOG</B> String => catalog of table that is the scope * of a reference attribute (<code>null</code> if DATA_TYPE isn't REF) * <LI><B>SCOPE_SCHEMA</B> String => schema of table that is the scope of * a reference attribute (<code>null</code> if DATA_TYPE isn't REF) * <LI><B>SCOPE_TABLE</B> String => table name that is the scope of a * reference attribute (<code>null</code> if the DATA_TYPE isn't REF) * <LI><B>SOURCE_DATA_TYPE</B> short => source type of a distinct type or * user-generated Ref type,SQL type from java.sql.Types (<code>null</code> * if DATA_TYPE isn't DISTINCT or user-generated REF) * </OL> * * @param catalog a catalog name; must match the catalog name as it is * stored in the database; "" retrieves those without a catalog; * <code>null</code> means that the catalog name should not be * used to narrow the search * @param schemaPattern a schema name pattern; must match the schema name as * it is stored in the database; "" retrieves those without a * schema; <code>null</code> means that the schema name should * not be used to narrow the search * @param typeNamePattern a type name pattern; must match the type name as * it is stored in the database * @param attributeNamePattern an attribute name pattern; must match the * attribute name as it is declared in the database * @return a <code>ResultSet</code> object in which each row is an * attribute description * @exception SQLException if a database access error occurs * @since 1.4 */ public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { throw new UnsupportedFeatureException("getAttributes"); } /** * Retrieves whether this database supports the given result set * holdability. * * @param holdability one of the following constants: * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or * <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code> * @return <code>true</code> if so; <code>false</code> otherwise * @exception SQLException if a database access error occurs * @see Connection * @since 1.4 */ public boolean supportsResultSetHoldability(int holdability) throws SQLException { return true; } /** * Retrieves the default holdability of this <code>ResultSet</code> * object. * * @return the default holdability; either * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or * <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code> * @exception SQLException if a database access error occurs * @since 1.4 */ public int getResultSetHoldability() throws SQLException { return ResultSet.HOLD_CURSORS_OVER_COMMIT; } /** * Retrieves the major version number of the underlying database. * * @return the underlying database's major version * @exception SQLException if a database access error occurs * @since 1.4 */ public int getDatabaseMajorVersion() throws SQLException { return m_connection.getVersionNumber()[0]; } /** * Retrieves the minor version number of the underlying database. * * @return underlying database's minor version * @exception SQLException if a database access error occurs * @since 1.4 */ public int getDatabaseMinorVersion() throws SQLException { return m_connection.getVersionNumber()[1]; } /** * Retrieves the major JDBC version number for this driver. * * @return JDBC version major number * @exception SQLException if a database access error occurs * @since 1.4 */ public int getJDBCMajorVersion() throws SQLException { return 4; // This class implements JDBC 4.0. } /** * Retrieves the minor JDBC version number for this driver. * * @return JDBC version minor number * @exception SQLException if a database access error occurs * @since 1.4 */ public int getJDBCMinorVersion() throws SQLException { return 0; // This class implements JDBC 4.0 } /** * Indicates whether the SQLSTATEs returned by * <code>SQLException.getSQLState</code> is X/Open (now known as Open * Group) SQL CLI or SQL99. * * @return the type of SQLSTATEs, one of: sqlStateXOpen or sqlStateSQL99 * @throws SQLException if a database access error occurs * @since 1.4 */ public int getSQLStateType() throws SQLException { return DatabaseMetaData.sqlStateSQL99; } /** * Indicates whether updates made to a LOB are made on a copy or directly to * the LOB. * * @return <code>true</code> if updates are made to a copy of the LOB; * <code>false</code> if updates are made directly to the LOB * @throws SQLException if a database access error occurs * @since 1.4 */ public boolean locatorsUpdateCopy() throws SQLException { /* * Currently LOB's aren't updateable at all, so it doesn't matter what * we return. We don't throw the notImplemented Exception because the * 1.5 JDK's CachedRowSet calls this method regardless of wether large * objects are used. */ return true; } /** * Retrieves weather this database supports statement pooling. * * @return <code>true</code> is so; <code>false</code> otherwise * @throws SQLException if a database access error occurs * @since 1.4 */ public boolean supportsStatementPooling() throws SQLException { return false; } /** * This method creates a ResultSet which is not associated with any * statement. */ private ResultSet createSyntheticResultSet(ResultSetField[] f, ArrayList tuples) throws SQLException { return new SyntheticResultSet(f, tuples); } // ************************************************************ // Non-implementation of JDBC 4 methods. // ************************************************************ public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getFunctionColumns" + "( String, String, String, String ) not implemented yet.", "0A000" ); } public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getFunctions( String, String, String ) not implemented yet.", "0A000" ); } public ResultSet getClientInfoProperties() throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getClientInfoProperties() not implemented yet.", "0A000" ); } public boolean autoCommitFailureClosesAllResultSets() throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.autoCommitFailureClosesAllResultSets() not implemented yet.", "0A000" ); } public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.supportsStoredFunctionsUsingCallSyntax() not implemented yet.", "0A000" ); } public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getSchemas( String, String ) not implemented yet.", "0A000" ); } public RowIdLifetime getRowIdLifetime() throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getRowIdLifetime() not implemented yet.", "0A000" ); } public boolean isWrapperFor(Class<?> c) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.isWrapperFor( Class< ? > ) not implemented yet.", "0A000" ); } public <T> T unwrap(java.lang.Class<T> T) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.unwrap( Class< T > ) not implemented yet.", "0A000" ); } public boolean generatedKeyAlwaysReturned() throws SQLException { return false; } public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { throw new SQLFeatureNotSupportedException( "SPIDatabaseMetadata.getPseudoColumns(String,String,String,String) not implemented yet.", "0A000" ); } }