/* Copyright (c) 2001-2024, 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.jdbc;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.hsqldb.error.ErrorCode;
import org.hsqldb.result.ResultMetaData;
import org.hsqldb.types.IntervalType;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

// fredt@users    - 20040412 - removed DITypeInfo dependencies
// campbell-burnet@users - 200404xx - removed unused imports;refinement for better
//                             usability of getColumnDisplaySize;
//                             javadoc updates
// campbell-burnet@users - 20051207 - patch 1.8.0.x initial JDBC 4.0 support work
// campbell-burnet@users - 20060522 - doc   1.9.0 full synch up to JAVA 1.6 (Mustang) Build 84
// fredt@users - 1.9.0 usage of types and new ResultMetadata and ColumnMetaData
// Revision 1.15  2006/07/12 12:29:42  boucherb

/**
 * An object that can be used to get information about the types
 * and properties of the columns in a {@code ResultSet} object.
 * The following code fragment creates the {@code ResultSet} object rs,
 * creates the {@code ResultSetMetaData} object rsmd, and uses rsmd
 * to find out how many columns rs has and whether the first column in rs
 * can be used in a {@code WHERE} clause.
 * <PRE>
 *
 *     ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM TABLE2");
 *     ResultSetMetaData rsmd = rs.getMetaData();
 *     int numberOfColumns = rsmd.getColumnCount();
 *     boolean b = rsmd.isSearchable(1);
 *
 * </PRE>
 *
 * <!-- start Release-specific documentation -->
 * <div class="ReleaseSpecificDocumentation">
 * <p class="rshead">HSQLDB-Specific Information:</p>
 *
 * HSQLDB supports a subset of the {@code ResultSetMetaData} interface.<p>
 *
 * The JDBC specification for {@code ResultSetMetaData} is in part very
 * vague. This causes potential incompatibility between interpretations of the
 * specification as realized in different JDBC driver implementations. As such,
 * deciding to what degree reporting ResultSetMetaData is accurate has been
 * considered very carefully. Hopefully, the design decisions made in light of
 * these considerations have yielded precisely the subset of full
 * ResultSetMetaData support that is most commonly needed and that is most
 * important, while also providing, under the most common use-cases, the
 * fastest access with the least overhead and the best compromise between
 * speed, accuracy, jar-footprint and retention of JDBC resources. <p>
 *
 * (fredt@users) <br>
 * (campbell-burnet@users)
 * </div>
 * <!-- end release-specific documentation -->
 *
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.7.3
 * @see JDBCStatement#executeQuery
 * @see JDBCStatement#getResultSet
 * @see java.sql.ResultSetMetaData
 */
public class JDBCResultSetMetaData implements ResultSetMetaData {

    /**
     * Returns the number of columns in this {@code ResultSet} object.
     *
     * @return the number of columns
     * @throws SQLException if a database access error occurs
     */
    public int getColumnCount() throws SQLException {
        return resultMetaData.getColumnCount();
    }

    /**
     * Indicates whether the designated column is automatically numbered.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 fully supports SQL Standard features T174 and T176 that
     * define identity column support.
     *
     * <hr>
     *
     * However, it must be stated here that contrary to the generic
     * documentation previous to the JDBC4 specification, HSQLDB automatically
     * numbered columns (IDENTITY columns, in HSQLDB parlance) are not
     * read-only. <p>
     *
     * In fact, the generic documentation previous to the JDBC4 specification
     * seems to contradict the general definition of what, at minimum,
     * an auto-increment column is: <p>
     *
     * Simply, an auto-increment column is one that guarantees it has a
     * autogenerated value after a successful insert or update operation,
     * even if no value is supplied, or DEFAULT is specified.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isAutoIncrement(int column) throws SQLException {
        checkColumn(column);

        return resultMetaData.columns[--column].isIdentity();
    }

    /**
     * Indicates whether a column's case matters.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 1.7.1 did not report this value accurately.  <p>
     *
     * Starting with 1.7.2, this feature is better supported.  <p>
     *
     * This method returns true for any column whose data type is a character
     * type, with the exception of VARCHAR_IGNORECASE for which it returns
     * false. It also returns false for any column whose data type is a
     * not a character data type.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isCaseSensitive(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        if (type.isCharacterType()) {
            return type.getCollation().isCaseSensitive();
        }

        return false;
    }

    /**
     * Indicates whether the designated column can be used in a where clause.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 handles this differently from previous versions. <p>
     *
     * If the column in question is a database table or view column, and the
     * type of the column allows searching, then returns true, otherwise false.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isSearchable(int column) throws SQLException {
        checkColumn(column);

        return resultMetaData.columns[--column].isSearchable();
    }

    /**
     * Indicates whether the designated column is a cash value.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 fully supports this feature and returns true for
     * NUMERIC and DECIMAL columns.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isCurrency(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return (type.typeCode == Types.SQL_DECIMAL
                || type.typeCode == Types.SQL_NUMERIC)
               && type.scale > 0;
    }

    /**
     * Indicates the nullability of values in the designated column.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.2 fully supports this feature.  <p>
     *
     * {@code columnNoNulls} is always returned for result set columns
     * that represent constants, sequences or table columns known
     * to be not null. {@code columnNullable} is returned for NULL constants,
     * or nullable table columns. {@code columnNullableUnknown} is returned
     * for all other columns such as aggregates and computed values.<p>
     *
     * To determine the nullable status of a table column in isolation from
     * ResultSetMetaData and in a DBMS-independent fashion, the
     * DatabaseMetaData.getColumns() method can be invoked with the
     * appropriate filter values and the result should be inspected at the
     * position described in the DatabaseMetaData.getColumns() API
     * documentation.
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return the nullability status of the given column; one of {@code columnNoNulls},
     *          {@code columnNullable} or {@code columnNullableUnknown}
     * @throws SQLException if a database access error occurs
     */
    public int isNullable(int column) throws SQLException {
        checkColumn(column);

        return resultMetaData.columns[--column].getNullability();
    }

    /**
     * Indicates whether values in the designated column are signed numbers.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 fully supports this feature.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isSigned(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.isNumberType();
    }

    /**
     * Indicates the designated column's normal maximum width in characters.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 fully supports this feature.  <p>
     *
     * The current calculation follows these rules:
     *
     * <ol>
     * <li>Long character types and datetime types:<p>
     *
     *     The maximum length/precision, respectively.
     *
     * <li>CHAR and VARCHAR types:
     *
     *      <ul>
     *      <li> If the result set column is a direct pass through of a table
     *           column value and column size was declared, then the declared
     *           value is returned.
     *
     *      <li> Otherwise, the computed length according to SQL Standard is
     *           returned. For very large values, the value of the system property
     *           hsqldb.max_xxxchar_display_size or the magic value
     *           32766 (0x7FFE) (tested usable/accepted by most tools and
     *           compatible with assumptions made by java.io read/write
     *           UTF) when the system property is not defined or is not
     *           accessible, due to security constraints.
     *
     *      </ul>
     *
     *      It must be noted that the latter value in no way affects the
     *      ability of the HSQLDB JDBC driver to retrieve longer values
     *      and serves only as the current best effort at providing a
     *      value that maximizes usability across a wide range of tools,
     *      given that the HSQLDB database engine allows very large
     *      lengths to be declared.
     *
     * <li>Number types: <p>
     *
     *     The max precision, plus the length of the negation character (1),
     *     plus (if applicable) the maximum number of characters that may
     *     occupy the exponent character sequence.  Note that some legacy tools
     *     do not correctly handle BIGINT values of greater than 18 digits.
     *
     * <li>BOOLEAN type: <p>
     *
     *     The length of the character sequence "false" (5), the longer of the
     *     two boolean value String representations.
     *
     * <li>Remaining types: <p>
     *
     *     The maximum length/precision, respectively, as reported by
     *     DatabaseMetaData.getTypeInfo(), when applicable.  If the maximum
     *     display size is unknown, unknowable or inapplicable, then zero is
     *     returned.
     *
     * </ol>
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return the normal maximum number of characters allowed as the width
     *          of the designated column
     * @throws SQLException if a database access error occurs
     */
    public int getColumnDisplaySize(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.displaySize();
    }

    /**
     * Gets the designated column's suggested title for use in printouts and
     * displays. The suggested title is usually specified by the SQL {@code AS}
     * clause.  If a SQL {@code AS} is not specified, the value returned from
     * {@code getColumnLabel} will be the same as the value returned by the
     * {@code getColumnName} method.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * In HSQLDB, a {@code ResultSet} column label is determined using the
     * following order of precedence:
     *
     * <OL>
     * <LI>The label (alias) specified in the generating query.</LI>
     * <LI>The name of the underlying column, if no label is specified.<br>
     * <LI>C1, C2, etc. for computed columns that have no label.</LI>
     * </OL>
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return the suggested column title
     * @throws SQLException if a database access error occurs
     */
    public String getColumnLabel(int column) throws SQLException {

        checkColumn(column--);

        String label = resultMetaData.columnLabels[column];

        if (label != null && label.length() > 0) {
            return label;
        }

        return resultMetaData.columns[column].getNameString();
    }

    /**
     * Get the designated column's name.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * In HSQLDB, a ResultSet column name is determined using the following
     * order of precedence:
     *
     * <OL>
     * <LI>The name of the underlying column, if the ResultSet column
     *   represents a column in a table.</LI>
     * <LI>The label or alias specified in the generating query.</LI>
     * <LI>C1, C2, etc. for computed columns that have no label.</LI>
     * </OL> <p>
     *
     * If the {@code jdbc.get_column_name} property of the JDBC Connection
     * has been set to false, this method returns the same value as
     * {@link #getColumnLabel(int)}.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return column name
     * @throws SQLException if a database access error occurs
     */
    public String getColumnName(int column) throws SQLException {

        checkColumn(column--);

        if (useColumnName) {
            String name = resultMetaData.columns[column].getNameString();

            if (name != null && name.length() > 0) {
                return name;
            }
        }

        String label = resultMetaData.columnLabels[column];

        return label == null
               ? resultMetaData.columns[column].getNameString()
               : label;
    }

    /**
     * Get the designated column's table's schema.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * Since 1.8.0.x, HSQLDB implements standard SQL SCHEMA support;
     * this method returns the actual schema of the column's table.
     * Columns generated in queries have no schema name.
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return schema name or "" if not applicable
     * @throws SQLException if a database access error occurs
     */
    public String getSchemaName(int column) throws SQLException {

        checkColumn(column);

        String name = resultMetaData.columns[--column].getSchemaNameString();

        return name == null
               ? ""
               : name;
    }

    /**
     * Get the designated column's specified column size.
     * For numeric data, this is the maximum precision.  For character data, this is the [maximum] length in characters.
     * For datetime datatypes, this is the [maximum] length in characters of the String representation (assuming the
     * maximum allowed precision of the fractional seconds component). For binary data, this is the [maximum] length in bytes.  For the ROWID datatype,
     * this is the length in bytes. 0 is returned for data types where the
     * column size is not applicable.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 reports the correct length or precision for
     * all columns. For DOUBLE, the binary precision of 64 is returned, while
     * for other numeric types the decimal precision is returned.
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return precision
     * @throws SQLException if a database access error occurs
     */
    public int getPrecision(int column) throws SQLException {

        checkColumn(column);

        // type in columnTypes overrides column type
        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.getJDBCPrecision();
    }

    /**
     * Gets the designated column's number of digits to right of the decimal point.
     * 0 is returned for data types where the scale is not applicable.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 reports the correct scale for all columns.<p>
     *
     * For datetime and interval types such as Timestamp or Time, the
     * fractional second precision is reported.<p>
     *
     * The reported scale for INTEGER, BIGINT and DOUBLE is 0.
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return scale
     * @throws SQLException if a database access error occurs
     */
    public int getScale(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.getJDBCScale();
    }

    /**
     * Gets the designated column's table name.
     *
     * @param column the first column is 1, the second is 2, ...
     * @return table name or "" if not applicable
     * @throws SQLException if a database access error occurs
     */
    public String getTableName(int column) throws SQLException {

        checkColumn(column);

        String name = resultMetaData.columns[--column].getTableNameString();

        return name == null
               ? ""
               : name;
    }

    /**
     * Gets the designated column's table's catalog name.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * From 2.0, HSQLDB returns the name of the catalog. The default name is
     * PUBLIC.
     * This value can be changed for the database using an SQL command.<p>
     *
     * HSQLDB supports use of catalog qualification in DLL or DML when it is
     * allowed by the Standard. <p>
     *
     * However, not all clients respect the SQL Standard and may use a
     * catalog qualifier in a context where it is not supported by the Standard.
     * <p>
     *
     * For greater detail, see discussion at:
     * {@link JDBCDatabaseMetaData}.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return the name of the catalog for the table in which the given column
     *          appears or "" if not applicable
     * @throws SQLException if a database access error occurs
     */
    public String getCatalogName(int column) throws SQLException {

        checkColumn(column);

        String name = resultMetaData.columns[--column].getCatalogNameString();

        return name == null
               ? ""
               : name;
    }

    /**
     * Retrieves the designated column's SQL type.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * This reports the SQL type code of the column. For time and timestamp
     * types that are WITH TIME ZONE, the values as the SQL Standard CLI
     * codes.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     *
     * @param column the first column is 1, the second is 2, ...
     * @return SQL type from java.sql.Types
     * @throws SQLException if a database access error occurs
     * @see java.sql.Types
     */
    public int getColumnType(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.getJDBCTypeCode();
    }

    /**
     * Retrieves the designated column's database-specific type name.
     *
     * @param column the first column is 1, the second is 2, ...
     * @return type name used by the database. If the column type is
     * a user-defined type, then a fully-qualified type name is returned.
     * @throws SQLException if a database access error occurs
     */
    public String getColumnTypeName(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.getNameString();
    }

    /**
     * Indicates whether the designated column is definitely not writable.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * From 2.0 this method returns true if the ResultSet is not updatable
     * or the column in question is not updatable.
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isReadOnly(int column) throws SQLException {
        checkColumn(column);

        return !resultMetaData.columns[--column].isWriteable();
    }

    /**
     * Indicates whether it is possible for a write on the designated column to
     * succeed.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * From 2.0 this method returns false if the ResultSet is not updatable
     * or the column in question is not updatable.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isWritable(int column) throws SQLException {

        checkColumn(column);

        return resultMetaData.colIndexes != null
               && resultMetaData.colIndexes[--column] > -1;
    }

    /**
     * Indicates whether a write on the designated column will definitely succeed.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * From 2.0 this method returns false if the ResultSet is not updatable
     * or the column in question is not updatable.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @param column the first column is 1, the second is 2, ...
     * @return {@code true} if so; {@code false} otherwise
     * @throws SQLException if a database access error occurs
     */
    public boolean isDefinitelyWritable(int column) throws SQLException {

        checkColumn(column);

        return resultMetaData.colIndexes != null
               && resultMetaData.colIndexes[--column] > -1;
    }

    //--------------------------JDBC 2.0-----------------------------------

    /**
     * <p>Returns the fully-qualified name of the Java class whose instances
     * are manufactured if the method {@code ResultSet.getObject}
     * is called to retrieve a value
     * from the column.  {@code ResultSet.getObject} may return a subclass of the
     * class returned by this method.
     *
     * <!-- start Release-specific documentation -->
     * <div class="ReleaseSpecificDocumentation">
     * <p class="rshead">HSQLDB-Specific Information:</p>
     *
     * HSQLDB 2.0 fully supports this feature.<p>
     *
     * For columns of type OTHER, there is no specific class name and
     * java.lang.Object is returned.
     *
     * </div>
     * <!-- end release-specific documentation -->
     *
     * @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} to retrieve the value in the specified
     * column. This is the class name used for custom mapping.
     * @throws SQLException if a database access error occurs
     * @since JDK 1.2
     */
    public String getColumnClassName(int column) throws SQLException {

        checkColumn(column);

        Type type = translateType(resultMetaData.columnTypes[--column]);

        return type.getJDBCClassName();
    }

    //----------------------------- JDBC 4.0 -----------------------------------
    // ------------------- java.sql.Wrapper implementation ---------------------

    /**
     * Returns an object that implements the given interface to allow access to
     * non-standard methods, or standard methods not exposed by the proxy.
     *
     * If the receiver implements the interface then the result is the receiver
     * or a proxy for the receiver. If the receiver is a wrapper
     * and the wrapped object implements the interface then the result is the
     * wrapped object or a proxy for the wrapped object. Otherwise return the
     * result of calling {@code unwrap} recursively on the wrapped object
     * or a proxy for that result. If the receiver is not a
     * wrapper and does not implement the interface, then an {@code SQLException} is thrown.
     *
     * @param iface A Class defining an interface that the result must implement.
     * @return an object that implements the interface. May be a proxy for the actual implementing object.
     * @throws java.sql.SQLException If no object found that implements the interface
     * @since JDK 1.6
     */
    @SuppressWarnings("unchecked")
    public <T> T unwrap(java.lang.Class<T> iface) throws java.sql.SQLException {

        if (isWrapperFor(iface)) {
            return (T) this;
        }

        throw JDBCUtil.invalidArgument("iface: " + iface);
    }

    /**
     * Returns true if this either implements the interface argument or is directly or indirectly a wrapper
     * for an object that does. Returns false otherwise. If this implements the interface then return true,
     * else if this is a wrapper then return the result of recursively calling {@code isWrapperFor} on the wrapped
     * object. If this does not implement the interface and is not a wrapper, return false.
     * This method should be implemented as a low-cost operation compared to {@code unwrap} so that
     * callers can use this method to avoid expensive {@code unwrap} calls that may fail. If this method
     * returns true then calling {@code unwrap} with the same argument should succeed.
     *
     * @param iface a Class defining an interface.
     * @return true if this implements the interface or directly or indirectly wraps an object that does.
     * @throws java.sql.SQLException  if an error occurs while determining whether this is a wrapper
     * for an object with the given interface.
     * @since JDK 1.6
     */
    public boolean isWrapperFor(
            java.lang.Class<?> iface)
            throws java.sql.SQLException {
        return (iface != null && iface.isAssignableFrom(this.getClass()));
    }

// ------------------------- Internal Implementation ---------------------------
    private ResultMetaData resultMetaData;

    /**
     * Whether to use the underlying column name or label when reporting
     * getColumnName().
     */
    private boolean useColumnName;
    private boolean translateTTIType;
    private int     columnCount;

    /**
     * Constructs a new JDBCResultSetMetaData object from the specified
     * object.
     *
     * @param meta the ResultMetaData object from which to construct a new
     *        JDBCResultSetMetaData object
     * @throws SQLException if a database access error occurs
     */
    JDBCResultSetMetaData(
            ResultMetaData meta,
            boolean isUpdatable,
            boolean isInsertable,
            JDBCConnection conn)
            throws SQLException {
        init(meta, conn);
    }

    /**
     *  Initializes this JDBCResultSetMetaData object from the specified
     *  Result and HsqlProperties objects.
     *
     *  @param meta the ResultMetaData object from which to initialize this
     *         JDBCResultSetMetaData object
     *  @param conn the JDBCConnection
     *  @throws SQLException if a database access error occurs
     */
    void init(ResultMetaData meta, JDBCConnection conn) throws SQLException {

        resultMetaData = meta;
        columnCount    = resultMetaData.getColumnCount();

        // fredt -  props is null for internal connections, so always use the
        //          default behaviour in this case
        // JDBCDriver.getPropertyInfo says
        // default is true
        useColumnName = true;

        if (conn == null) {
            return;
        }

        useColumnName    = conn.isUseColumnName;
        translateTTIType = conn.isTranslateTTIType;
    }

    /**
     * Performs an internal check for column index validity.
     *
     * @param column index of column to check
     * @throws SQLException when this object's parent ResultSet has
     *      no such column
     */
    private void checkColumn(int column) throws SQLException {

        if (column < 1 || column > columnCount) {
            throw JDBCUtil.sqlException(
                ErrorCode.JDBC_COLUMN_NOT_FOUND,
                String.valueOf(column));
        }
    }

    /**
     * Translates an INTERVAL type to VARCHAR.
     * Removes time zone from datetime types.
     *
     */
    private Type translateType(Type type) {

        if (this.translateTTIType) {
            if (type.isIntervalType()) {
                type = ((IntervalType) type).getCharacterType();
            }
        }

        return type;
    }

    /**
     * Returns a string representation of the object. <p>
     *
     * The string consists of the name of the class of which the
     * object is an instance, the at-sign character `{@code @}',
     * the unsigned hexadecimal representation of the hash code of the
     * object and a comma-delimited list of this object's indexed attributes,
     * enclosed in square brackets.
     *
     * @return  a string representation of the object.
     */
    public String toString() {

        StringBuilder sb = new StringBuilder();

        sb.append(super.toString());

        if (columnCount == 0) {
            sb.append("[columnCount=0]");

            return sb.toString();
        }

        sb.append('[');

        for (int i = 0; i < columnCount; i++) {
            JDBCColumnMetaData meta = getColumnMetaData(i + 1);

            sb.append('\n');
            sb.append("   column_");
            sb.append(i + 1);
            sb.append('=');
            sb.append(meta);

            if (i + 1 < columnCount) {
                sb.append(',');
                sb.append(' ');
            }
        }

        sb.append('\n');
        sb.append(']');

        return sb.toString();
    }

    JDBCColumnMetaData getColumnMetaData(int i) {

        JDBCColumnMetaData meta = new JDBCColumnMetaData();

        try {
            meta.catalogName          = getCatalogName(i);
            meta.columnClassName      = getColumnClassName(i);
            meta.columnDisplaySize    = getColumnDisplaySize(i);
            meta.columnLabel          = getColumnLabel(i);
            meta.columnName           = getColumnName(i);
            meta.columnType           = getColumnType(i);
            meta.isAutoIncrement      = isAutoIncrement(i);
            meta.isCaseSensitive      = isCaseSensitive(i);
            meta.isCurrency           = isCurrency(i);
            meta.isDefinitelyWritable = isDefinitelyWritable(i);
            meta.isNullable           = isNullable(i);
            meta.isReadOnly           = isReadOnly(i);
            meta.isSearchable         = isSearchable(i);
            meta.isSigned             = isSigned(i);
            meta.isWritable           = isWritable(i);
            meta.precision            = getPrecision(i);
            meta.scale                = getScale(i);
            meta.schemaName           = getSchemaName(i);
            meta.tableName            = getTableName(i);
        } catch (SQLException e) {}

        return meta;
    }
}
