/* Copyright (c) 2001-2009, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb.dbinfo; import java.util.Locale; import org.hsqldb.ColumnSchema; import org.hsqldb.Table; import org.hsqldb.Types; import org.hsqldb.resources.BundleHandler; import org.hsqldb.store.ValuePool; import org.hsqldb.TableBase; import org.hsqldb.TextTable; /** * Provides extended information about HSQLDB tables and their * columns/indices. <p> * * @author boucherb@users * @version 1.8.0 * @since 1.7.2 */ final class DITableInfo { // related to DatabaseMetaData int bestRowTemporary = 0; int bestRowTransaction = 1; int bestRowSession = 2; int bestRowUnknown = 0; int bestRowNotPseudo = 1; static final short tableIndexOther = 3; /** Used in buffer size and character octet length determinations. */ private static final int HALF_MAX_INT = Integer.MAX_VALUE >>> 1; /** BundleHandler id for column remarks resource bundle. */ private int hnd_column_remarks = -1; /** BundleHandler id for table remarks resource bundle. */ private int hnd_table_remarks = -1; /** The Table object upon which this object is reporting. */ private Table table; /** Provides intrinsic type infformation support. */ private static final DITypeInfo ti = new DITypeInfo(); /** * Creates a new DITableInfo object with the default Locale and reporting * on no table. It is absolutely essential the a valid Table object is * assigned to this object, using the setTable method, before any Table, * Column or Index oriented value retrieval methods are called; this class * contains no assertions or exception handling related to a null or * invalid table member attribute. */ DITableInfo() { setupBundles(); } /** * Sets the Locale for table and column remarks. <p> */ void setupBundles() { Locale oldLocale; synchronized (BundleHandler.class) { oldLocale = BundleHandler.getLocale(); BundleHandler.setLocale(Locale.getDefault()); hnd_column_remarks = BundleHandler.getBundleHandle("column-remarks", null); hnd_table_remarks = BundleHandler.getBundleHandle("table-remarks", null); BundleHandler.setLocale(oldLocale); } } /** * Retrieves whether the best row identifier column is * a pseudo column, like an Oracle ROWID. <p> * * Currently, this always returns an Integer whose value is * DatabaseMetaData.bestRowNotPseudo, as HSQLDB does not support * pseudo columns such as ROWID. <p> * * @return whether the best row identifier column is * a pseudo column */ Integer getBRIPseudo() { return ValuePool.getInt(bestRowNotPseudo); } /** * Retrieves the scope of the best row identifier. <p> * * This implements the rules described in * DatabaseInformationMain.SYSTEM_BESTROWIDENTIFIER. <p> * * @return the scope of the best row identifier */ Integer getBRIScope() { return (table.isWritable()) ? ValuePool.getInt(bestRowTemporary) : ValuePool.getInt(bestRowSession); } /** * Retrieves, if definitely known, the transfer size for values of the * specified column, in bytes. <p> * * @param i zero-based column index * @return the transfer size for values of the * specified column, in bytes */ Integer getColBufLen(int i) { int size; int type; ColumnSchema column; column = table.getColumn(i); type = column.getDataType().getJDBCTypeCode(); switch (type) { case Types.SQL_CHAR : case Types.SQL_CLOB : case Types.VARCHAR_IGNORECASE : case Types.SQL_VARCHAR : { size = column.getDataType().precision > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) column.getDataType().precision; if (size == 0) {} else if (size > HALF_MAX_INT) { size = 0; } else { size = 2 * size; } break; } case Types.SQL_BINARY : case Types.SQL_BLOB : case Types.SQL_VARBINARY : { size = column.getDataType().precision > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) column.getDataType().precision; break; } case Types.SQL_BIGINT : case Types.SQL_DOUBLE : case Types.SQL_FLOAT : case Types.SQL_DATE : case Types.SQL_REAL : case Types.SQL_TIME_WITH_TIME_ZONE : case Types.SQL_TIME : { size = 8; break; } case Types.SQL_TIMESTAMP_WITH_TIME_ZONE : case Types.SQL_TIMESTAMP : { size = 12; break; } case Types.SQL_INTEGER : case Types.SQL_SMALLINT : case Types.TINYINT : { size = 4; break; } case Types.SQL_BOOLEAN : { size = 1; break; } default : { size = 0; break; } } return (size > 0) ? ValuePool.getInt(size) : null; } /** * Retrieves the declared size, in bytes, for character-valued * columns. <p> * * If the size cannot be represented in the range [0,Integer.MAX_VALUE], * this returns null. <p> * * @param i zero-based column index * @return the size, in bytes, for character-valued columns */ Integer getColCharOctLen(int i) { int size; int type; ColumnSchema column; column = table.getColumn(i); type = column.getDataType().getJDBCTypeCode(); switch (type) { case Types.SQL_CHAR : case Types.SQL_CLOB : case Types.VARCHAR_IGNORECASE : case Types.SQL_VARCHAR : { size = column.getDataType().precision > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) column.getDataType().precision; if (size == 0) {} else if (size > HALF_MAX_INT) { size = 0; } else { size = 2 * size; } break; } default : { size = 0; break; } } return (size == 0) ? null : ValuePool.getInt(size); } /** * Retrieves the SQL data type code for the specified column. <p> * * @param i zero-based column index * @return the SQL data type code for the specified column */ Integer getColJDBCDataType(int i) { return ValuePool.getInt( table.getColumn(i).getDataType().getJDBCTypeCode()); } /** * Retrieves the SQL data type name for the specified column. <p> * * @param i zero-based column index * @return the SQL data type name for the specified column */ String getColDataTypeName(int i) { return table.getColumn(i).getDataType().getNameString(); } /** * Retrieves the HSQLDB data subtype code for the specified column. <p> * * @param i zero-based column index * @return the HSQLDB data subtype code for the specified column */ Integer getColDataTypeSub(int i) { int type = table.getColumn(i).getDataType().getJDBCTypeCode(); int sub = type == Types.VARCHAR_IGNORECASE ? Types.TYPE_SUB_IGNORECASE : Types.TYPE_SUB_DEFAULT; return ValuePool.getInt(sub); } /** * Retrieves the declared default value expression for the column. <p> * * @param i zero-based column index * @return the declared default value expression for the column */ String getColDefault(int i) { return table.getColumn(i).getDefaultSQL(); } /** * Retrieves whether the specified column is the identity column for * the table. <p> * * @param i zero-based column index * @return whether the specified column is the identity column for * the table. */ Boolean getColIsIdentity(int i) { return table.getColumn(i).isIdentity() ? Boolean.TRUE : Boolean.FALSE; } /** * Retrieves whether the specified column is nullable. <p> * * If the column is nullable, "YES" is retrieved, else "NO". <p> * * @param i zero-based column index * @return the nullability of the specified column */ String getColIsNullable(int i) { ColumnSchema column = table.getColumn(i); return (column.isNullable() && !column.isPrimaryKey()) ? "YES" : "NO"; } /** * Retrieves the simple name of the specified column. <p> * * @param i zero-based column index * @return the simple name of the specified column. */ String getColName(int i) { return table.getColumn(i).getName().name; } /** * Retrieves the specified column's nullablility. <p> * * @param i zero-based column index * @return the specified column's nullablilit */ Integer getColNullability(int i) { ColumnSchema column = table.getColumn(i); return (column.isNullable() && !column.isPrimaryKey()) ? ValuePool.getInt(DITypeInfo.columnNullable) : ValuePool.getInt(DITypeInfo.columnNoNulls); } /** * Retrieves the number base that should be used to interpret the * specified column's numeric precision, as reported by getColSize(int). * * @param i zero-based column index * @return the number base that should be used to * interpret the column's numeric precision, * as reported by getColSize(int). */ Integer getColPrecRadix(int i) { ti.setTypeCode(table.getColumn(i).getDataType().getJDBCTypeCode()); return ti.getNumPrecRadix(); } /** * Retrieves the remarks, if any, recorded against the specified * column. <p> * * @param i zero-based column index * @return the remarks recorded against the specified column. */ String getColRemarks(int i) { String key; if (table.getTableType() != TableBase.SYSTEM_TABLE) { return null; } key = getName() + "_" + getColName(i); return BundleHandler.getString(hnd_column_remarks, key); } /** * Retrieves the declared (but not currently enforced) or implicit fixed * number of digits to the right of the decimal point for exact numeric * types. * * If the column's type precludes scale declaration, null is returned. * * @param i zero-based column index * @return the fixed number of digits to the right of the decimal point * for exact numeric types. */ Integer getColScaleOrNull(int i) { ColumnSchema column; int type; column = table.getColumn(i); type = column.getDataType().getJDBCTypeCode(); return Types.acceptsScaleCreateParam(type) ? ValuePool.getInt(column.getDataType().scale) : null; } /** * Retrieves null (not implemented). <p> * * @param i zero-based column index * @return null (not implemented) */ String getColScopeCat(int i) { return null; } /** * Retrieves null (not implemented). <p> * * @param i zero-based column index * @return null (not implemented) */ String getColScopeSchem(int i) { return null; } /** * Retrieves null (not implemented). <p> * * @param i zero-based column index * @return null (not implemented) */ String getColScopeTable(int i) { return null; } /** * Retrieves either the declared or maximum length/precision for * the specified column, if its type allows a precision/length * declaration, else null. <p> * * @param i zero-based column index * @return the declared or maximum length/precision for * the specified column */ Integer getColSize(int i) { ColumnSchema column; int type; int size; column = table.getColumn(i); type = column.getDataType().getJDBCTypeCode(); if (!Types.acceptsPrecision(type)) { return null; } size = column.getDataType().precision > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) column.getDataType().precision; if (size > 0) { return ValuePool.getInt(size); } else { ti.setTypeCode(type); return ti.getPrecision(); } } /** * Retrieves the SQL CLI data type code for the specified column. <p> * * @param i zero-based column index * @return the SQL CLI data type code for the specified column */ Integer getColSqlDataType(int i) { ti.setTypeCode(table.getColumn(i).getDataType().getJDBCTypeCode()); return ti.getSqlDataType(); } /** * Retrieves the SQL CLI datetime subtype for the specified column. <p> * * @param i zero-based column index * @return the SQL CLI datetime subtype for the specified column */ Integer getColSqlDateTimeSub(int i) { ti.setTypeCode(table.getColumn(i).getDataType().getJDBCTypeCode()); return ti.getSqlDateTimeSub(); } /** * Retrieves the full data source descriptor for [TEMP] TEXT tables. <p> * * @return the full data source descriptor */ String getDataSource() { return table.isText() ? ((TextTable) table).getDataSource() : null; } /** * Retrieves the HSQLDB-specific type of the table. <p> * * @return the HSQLDB-specific type of the table */ String getHsqlType() { switch (table.getTableType()) { case TableBase.MEMORY_TABLE : case TableBase.TEMP_TABLE : case TableBase.SYSTEM_TABLE : return "MEMORY"; case TableBase.CACHED_TABLE : return "CACHED"; case TableBase.TEMP_TEXT_TABLE : case TableBase.TEXT_TABLE : return "TEXT"; case TableBase.VIEW_TABLE : default : return null; } } /** * Retrieves null (not implemented). <p> * * @param i zero-based index specifier * @return null (not implemented) */ Integer getIndexCardinality(int i) { return null; } /** * Retrieves the sort-direction for the specified column in the * specified index. <p> * * @param i zero-based index specifier * @param columnPosition zero-based ordinal position of column in index * @return the sort-direction for the specified column in the * specified index */ String getIndexColDirection(int i, int columnPosition) { // so far, hsqldb only supports completely ascending indexes return "A"; } /** * Retrieves an array map from the zero-based ordinal positions of the * columns in the specfied Index to the zero-based ordinal positions of * the same columns in the table. <p> * * @param i zero-based index specifier * @return an array map from the zero-based ordinal positions of * the columns in the specfied Index to the zero-based * ordinal positions of the same columns in the table */ int[] getIndexColumns(int i) { return table.getIndex(i).getColumns(); } /** * Retrieves the simple name of the specified Index. <p> * * @param i zero-based index specifier * @return the simple name of the specified Index */ String getIndexName(int i) { return table.getIndex(i).getName().name; } /** * Retrieves null (not implemented). <p> * * @param i zero-based index specifier * @return null (not implemented) */ Integer getIndexRowCardinality(int i) { return null; } /** * Retrieves the DatabaseMetaData type code of the specified Index. <p> * * @param i zero-based index specifier * @return the DatabaseMetaData type code of the specified Index */ Integer getIndexType(int i) { return ValuePool.getInt(tableIndexOther); } /** * Retrieves the number of visible columns in the specified Index. That * is, this retrieves one less than the physical number of columns if the * table maintains an internal primary index on an internal identity * column, as is the case when the table has no declared primary key or * identity column. <p> * * @param i zero-based index specifier * @return the number of visible columns in the specified Index */ int getIndexVisibleColumns(int i) { return table.getIndex(i).getColumnCount(); } /** * Retrieves the simple name of the table. <p> * * @return the simple name of the table */ String getName() { return table.getName().name; } /** * Retrieves the value of the next automatically assigned identity. <p> * * Be aware that this is not necessarily the value that will be assigned * to the identity column during the next insert or update. This value is * used if NULL is either implicitly or explicity assigned. <p> * * @return the value of the next automatically assigned identity */ Long getNextIdentity() { if (table.hasIdentityColumn()) { return ValuePool.getLong(table.getNextIdentity()); } else { return null; } } /** * Retrieves the remarks (if any) recorded against the Table. <p> * * @return the remarks recorded against the Table */ String getRemark() { return (table.getTableType() == TableBase.SYSTEM_TABLE) ? BundleHandler.getString(hnd_table_remarks, getName()) : null; } /** * Retrieves the standard JDBC type of the table. <p> * * "TABLE" for user-defined tables, "VIEW" for user-defined views, * and so on. * * @return the standard JDBC type of the table */ String getJDBCStandardType() { switch (table.getTableType()) { case TableBase.VIEW_TABLE : return "VIEW"; case TableBase.TEMP_TABLE : case TableBase.TEMP_TEXT_TABLE : return "GLOBAL TEMPORARY"; case TableBase.SYSTEM_TABLE : return "SYSTEM TABLE"; default : return "TABLE"; } } /** * Retrieves the Table object on which this object is currently * reporting. <p> * * @return the Table object on which this object * is currently reporting */ Table getTable() { return this.table; } /** * Retrieves, for [TEMP] TEXT tables, whether the table's data source * descriptor requests descending read semantics. That is, when this * value is true, it indicate that the text file is to be read from * the bottom up. <p> * * @return whether the table's data source * descriptor requests descending * read semantics */ Boolean isDataSourceDescending() { if (table.isText()) { return ((TextTable) table).isDescDataSource() ? Boolean.TRUE : Boolean.FALSE; } return Boolean.FALSE; } /** * Retrieves whether the specified Index is non-unique. <p> * * @param i zero-based index specifier * @return whether the specified Index is non-unique */ Boolean isIndexNonUnique(int i) { return ValuePool.getBoolean(!table.getIndex(i).isUnique()); } /** * Retrieves whether the table is in data read-only mode. This value does * not reflect the various read-only modes of the database or the * read-only mode of the connection. <p> * * @return whether the table is in data read-only mode */ Boolean isReadOnly() { return ValuePool.getBoolean(table.isDataReadOnly()); } /** * Assigns the Table object on which this object is to report. <p> * * @param table the Table object on which this object is to report */ void setTable(Table table) { this.table = table; } }