/***************************************************************************** * Copyright (C) 2008 EnterpriseDB Corporation. * Copyright (C) 2011 Stado Global Development Group. * * This file is part of Stado. * * Stado is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Stado is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Stado. If not, see <http://www.gnu.org/licenses/>. * * You can find Stado at http://www.stado.us * ****************************************************************************/ package org.postgresql.driver.jdbc2; import org.postgresql.driver.PGResultSetMetaData; import org.postgresql.driver.core.*; import org.postgresql.driver.util.GT; import org.postgresql.driver.util.PSQLException; import org.postgresql.driver.util.PSQLState; import java.sql.*; import java.util.Hashtable; public abstract class AbstractJdbc2ResultSetMetaData implements PGResultSetMetaData { protected final BaseConnection connection; protected final Field[] fields; private Hashtable tableNameCache; private Hashtable schemaNameCache; /* * Initialise for a result with a tuple set and * a field descriptor set * * @param fields the array of field descriptors */ public AbstractJdbc2ResultSetMetaData(BaseConnection connection, Field[] fields) { this.connection = connection; this.fields = fields; } /* * Whats the number of columns in the ResultSet? * * @return the number * @exception SQLException if a database access error occurs */ public int getColumnCount() throws SQLException { return fields.length; } /* * Is the column automatically numbered (and thus read-only) * I believe that PostgreSQL does not support this feature. * * @param column the first column is 1, the second is 2... * @return true if so * @exception SQLException if a database access error occurs */ public boolean isAutoIncrement(int column) throws SQLException { Field field = getField(column); return field.getAutoIncrement(connection); } /* * Does a column's case matter? ASSUMPTION: Any field that is * not obviously case insensitive is assumed to be case sensitive * * @param column the first column is 1, the second is 2... * @return true if so * @exception SQLException if a database access error occurs */ public boolean isCaseSensitive(int column) throws SQLException { Field field = getField(column); return connection.getTypeInfo().isCaseSensitive(field.getOID()); } /* * Can the column be used in a WHERE clause? Basically for * this, I split the functions into two types: recognised * types (which are always useable), and OTHER types (which * may or may not be useable). The OTHER types, for now, I * will assume they are useable. We should really query the * catalog to see if they are useable. * * @param column the first column is 1, the second is 2... * @return true if they can be used in a WHERE clause * @exception SQLException if a database access error occurs */ public boolean isSearchable(int column) throws SQLException { return true; } /* * Is the column a cash value? 6.1 introduced the cash/money * type, which haven't been incorporated as of 970414, so I * just check the type name for both 'cash' and 'money' * * @param column the first column is 1, the second is 2... * @return true if its a cash column * @exception SQLException if a database access error occurs */ public boolean isCurrency(int column) throws SQLException { String type_name = getPGType(column); return type_name.equals("cash") || type_name.equals("money"); } /* * Indicates the nullability of values in the designated column. * * @param column the first column is 1, the second is 2... * @return one of the columnNullable values * @exception SQLException if a database access error occurs */ public int isNullable(int column) throws SQLException { Field field = getField(column); return field.getNullable(connection); } /* * Is the column a signed number? In PostgreSQL, all numbers * are signed, so this is trivial. However, strings are not * signed (duh!) * * @param column the first column is 1, the second is 2... * @return true if so * @exception SQLException if a database access error occurs */ public boolean isSigned(int column) throws SQLException { Field field = getField(column); return connection.getTypeInfo().isSigned(field.getOID()); } /* * What is the column's normal maximum width in characters? * * @param column the first column is 1, the second is 2, etc. * @return the maximum width * @exception SQLException if a database access error occurs */ public int getColumnDisplaySize(int column) throws SQLException { Field field = getField(column); return connection.getTypeInfo().getDisplaySize(field.getOID(), field.getMod()); } /* * @param column the first column is 1, the second is 2, etc. * @return the column label * @exception SQLException if a database access error occurs */ public String getColumnLabel(int column) throws SQLException { Field field = getField(column); return field.getColumnLabel(); } /* * What's a column's name? * * @param column the first column is 1, the second is 2, etc. * @return the column name * @exception SQLException if a database access error occurs */ public String getColumnName(int column) throws SQLException { return getColumnLabel(column); } public String getBaseColumnName(int column) throws SQLException { Field field = getField(column); return field.getColumnName(connection); } /* * @param column the first column is 1, the second is 2... * @return the Schema Name * @exception SQLException if a database access error occurs */ public String getSchemaName(int column) throws SQLException { return ""; } public String getBaseSchemaName(int column) throws SQLException { Field field = getField(column); if (field.getTableOid() == 0) { return ""; } Integer tableOid = new Integer(field.getTableOid()); if (schemaNameCache == null) { schemaNameCache = new Hashtable(); } String schemaName = (String) schemaNameCache.get(tableOid); if (schemaName != null) { return schemaName; } else { ResultSet res = null; PreparedStatement ps = null; try { String sql = "SELECT n.nspname FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE n.oid = c.relnamespace AND c.oid = ?;"; ps = ((Connection)connection).prepareStatement(sql); ps.setInt(1, tableOid.intValue()); res = ps.executeQuery(); schemaName = ""; if (res.next()) { schemaName = res.getString(1); } schemaNameCache.put(tableOid, schemaName); return schemaName; } finally { if (res != null) res.close(); if (ps != null) ps.close(); } } } /* * What is a column's number of decimal digits. * * @param column the first column is 1, the second is 2... * @return the precision * @exception SQLException if a database access error occurs */ public int getPrecision(int column) throws SQLException { Field field = getField(column); return connection.getTypeInfo().getPrecision(field.getOID(), field.getMod()); } /* * What is a column's number of digits to the right of the * decimal point? * * @param column the first column is 1, the second is 2... * @return the scale * @exception SQLException if a database access error occurs */ public int getScale(int column) throws SQLException { Field field = getField(column); return connection.getTypeInfo().getScale(field.getOID(), field.getMod()); } /* * @param column the first column is 1, the second is 2... * @return column name, or "" if not applicable * @exception SQLException if a database access error occurs */ public String getTableName(int column) throws SQLException { return ""; } public String getBaseTableName(int column) throws SQLException { Field field = getField(column); if (field.getTableOid() == 0) { return ""; } Integer tableOid = new Integer(field.getTableOid()); if (tableNameCache == null) { tableNameCache = new Hashtable(); } String tableName = (String) tableNameCache.get(tableOid); if (tableName != null) { return tableName; } else { ResultSet res = null; PreparedStatement ps = null; try { ps = ((Connection)connection).prepareStatement("SELECT relname FROM pg_catalog.pg_class WHERE oid = ?"); ps.setInt(1, tableOid.intValue()); res = ps.executeQuery(); tableName = ""; if (res.next()) { tableName = res.getString(1); } tableNameCache.put(tableOid, tableName); return tableName; } finally { if (res != null) res.close(); if (ps != null) ps.close(); } } } /* * What's a column's table's catalog name? As with getSchemaName(), * we can say that if getTableName() returns n/a, then we can too - * otherwise, we need to work on it. * * @param column the first column is 1, the second is 2... * @return catalog name, or "" if not applicable * @exception SQLException if a database access error occurs */ public String getCatalogName(int column) throws SQLException { return ""; } /* * What is a column's SQL Type? (java.sql.Type int) * * @param column the first column is 1, the second is 2, etc. * @return the java.sql.Type value * @exception SQLException if a database access error occurs * @see org.postgresql.Field#getSQLType * @see java.sql.Types */ public int getColumnType(int column) throws SQLException { return getSQLType(column); } /* * Whats is the column's data source specific type name? * * @param column the first column is 1, the second is 2, etc. * @return the type name * @exception SQLException if a database access error occurs */ public String getColumnTypeName(int column) throws SQLException { return getPGType(column); } /* * Is the column definitely not writable? In reality, we would * have to check the GRANT/REVOKE stuff for this to be effective, * and I haven't really looked into that yet, so this will get * re-visited. * * @param column the first column is 1, the second is 2, etc. * @return true if so * @exception SQLException if a database access error occurs */ public boolean isReadOnly(int column) throws SQLException { return false; } /* * Is it possible for a write on the column to succeed? Again, we * would in reality have to check the GRANT/REVOKE stuff, which * I haven't worked with as yet. However, if it isn't ReadOnly, then * it is obviously writable. * * @param column the first column is 1, the second is 2, etc. * @return true if so * @exception SQLException if a database access error occurs */ public boolean isWritable(int column) throws SQLException { return !isReadOnly(column); } /* * Will a write on this column definately succeed? Hmmm...this * is a bad one, since the two preceding functions have not been * really defined. I cannot tell is the short answer. I thus * return isWritable() just to give us an idea. * * @param column the first column is 1, the second is 2, etc.. * @return true if so * @exception SQLException if a database access error occurs */ public boolean isDefinitelyWritable(int column) throws SQLException { return false; } // ******************************************************** // END OF PUBLIC INTERFACE // ******************************************************** /* * For several routines in this package, we need to convert * a columnIndex into a Field[] descriptor. Rather than do * the same code several times, here it is. * * @param columnIndex the first column is 1, the second is 2... * @return the Field description * @exception SQLException if a database access error occurs */ protected Field getField(int columnIndex) throws SQLException { if (columnIndex < 1 || columnIndex > fields.length) throw new PSQLException(GT.tr("The column index is out of range: {0}, number of columns: {1}.", new Object[]{new Integer(columnIndex), new Integer(fields.length)}), PSQLState.INVALID_PARAMETER_VALUE ); return fields[columnIndex - 1]; } protected String getPGType(int columnIndex) throws SQLException { return connection.getTypeInfo().getPGType(getField(columnIndex).getOID()); } protected int getSQLType(int columnIndex) throws SQLException { return connection.getTypeInfo().getSQLType(getField(columnIndex).getOID()); } // ** JDBC 2 Extensions ** // This can hook into our PG_Object mechanism /** * Returns the fully-qualified name of the Java class whose instances * are manufactured if the method <code>ResultSet.getObject</code> * is called to retrieve a value from the column. * * <code>ResultSet.getObject</code> may return a subclass of the class * returned by this method. * * @param column the first column is 1, the second is 2, ... * @return the fully-qualified name of the class in the Java programming * language that would be used by the method * <code>ResultSet.getObject</code> to retrieve the value in the specified * column. This is the class name used for custom mapping. * @exception SQLException if a database access error occurs */ public String getColumnClassName(int column) throws SQLException { Field field = getField(column); String result = connection.getTypeInfo().getJavaClass(field.getOID()); if (result != null) return result; int sqlType = getSQLType(column); switch(sqlType) { case Types.ARRAY: return ("java.sql.Array"); default: String type = getPGType(column); if ("unknown".equals(type)) { return ("java.lang.String"); } return ("java.lang.Object"); } } }