/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program 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 2
    of the License, or (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/

package com.sap.dbtech.jdbc;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import com.sap.dbtech.util.VersionInfo;
/**
 *
 */
public class DatabaseMetaDataSapDB
        extends DatabaseMetaDataBase
{
    public final static String defaultCatalogName = "";
    private VersionInfo dbVersionInfo = null;
    private String dbVersion = null;
    private String dbName = null;
    private boolean noNullableColumns = false; // default is try 'NULLABLE'
    private final String typename2odbc_C =
          "'CHAR', 1, "
        + "'CHAR() ASCII', 1, "
        + "'CHAR() EBCDIC', 1, "
        + "'CHAR() UNICODE', 1, "
        + "'CHAR() BYTE', -2, "
        + "'VARCHAR', 12, "
        + "'VARCHAR() ASCII', 12, "
        + "'VARCHAR() EBCDIC', 12, "
        + "'VARCHAR() UNICODE', 12, "
        + "'VARCHAR() BYTE', -3, "
        + "'LONG', -1, "
        + "'LONG ASCII', -1, "
        + "'LONG EBCDIC', -1, "
        + "'LONG UNICODE', -1, "
        + "'LONG BYTE', -4, "
        + "'LONG RAW', -4, "
        + "'FIXED', 3, "
        + "'DECIMAL', 3, "
        + "'REAL', 7, "
        + "'FLOAT', 6, "
        + "'DOUBLE PRECISION', 8, "
        + "'SMALLINT', 5, "
        + "'INTEGER', 4, "
        + "'BOOLEAN', -7, "
        + "'TIME', 92, "
        + "'DATE', 91, "
        + "'TIMESTAMP', 93, "
        + "'NUMBER', 2, "
        + "1111";

    private final String DataTypeSuffix_C =
          "'CHAR','CHAR()',"
        + "'VARCHAR','VARCHAR()',"
        + "'LONG','LONG',"
        + "'LONG RAW','LONG',"
        + "datatype";

    /**
     * DatabaseMetaDataSapDBTech constructor comment.
     */
    DatabaseMetaDataSapDB(ConnectionSapDB connection, VersionInfo mydbVersionInfo)
        throws SQLException
    {
        super(connection);
        this.dbName = connection.getConnectProperty(DriverSapDB.dbName_C);
        this.dbVersionInfo =   mydbVersionInfo;
    }

    /**
     * getBestRowIdentifier method comment.
     */
    public ResultSet getBestRowIdentifier(
        String catalog,
        String schema,
        String table,
        int scope,
        boolean nullable)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();
        ResultSet result;

        cmd.append ("SELECT "
            + "2 SCOPE, "     // bestRowSession
            + "columnname COLUMN_NAME, "
            + "decode (((decode (datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype))), "
            + typename2odbc_C + ") DATA_TYPE, "
            + "(decode(datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype)) TYPE_NAME, "
            + "len COLUMN_SIZE, "
            + "NULL BUFFER_LENGTH, "
            + "dec DECIMAL_DIGITS, "
            + "1 PSEUDO_COLUMN "   // bestRowNotPseudo
            + "FROM domain.columns ");
        cmd.append ("WHERE tablename = '" + table + "' ");
        if (this.realQualification(schema)) {
            cmd.append ("AND owner = '" + schema + "' ");
        }
        cmd.append ("AND keypos is not null ");
        try {
            result= this.internalQuery(cmd.toString(),"getBestRowIdentifier");
            try { ((ResultSetSapDB)result).setFromMetaData(true); } catch(ClassCastException ccx) {}
        }
        catch (SQLException exc) {
            if (exc.getErrorCode () == 100) {
                String [] colHeadings = {"SCOPE", "COLUMN_NAME",
                        "DATA_TYPE", "TYPE_NAME", "COLUMN_SIZE",
                        "BUFFER_LENGTH", "DECIMAL_DIGITS", "PSEUDO_COLUMN"};
                Object [][] rows = { {
                    new Integer (DatabaseMetaData.bestRowSession),
                    "SYSKEY",
                    new Integer (Types.BINARY),
                    "CHARBYTE",
                    new Integer (8),
                    null,
                    null,
                    new Integer (DatabaseMetaData.bestRowPseudo),
                    }};
                result = new MemoryResultSetSapDB (colHeadings, rows);
            }
            else {
                throw exc;
            }
        }
        return result;
    }
    /**
     * getCatalogs method comment.
     */
    public ResultSet getCatalogs() throws SQLException {
        ResultSet result;
        String [] colHeadings = {"TABLE_CAT"};
        Object [][] rows = {{defaultCatalogName},};

        result = new MemoryResultSetSapDB (colHeadings, rows);
        return result;
    }

    /**
     * getColumnPrivileges method comment.
     */
    public ResultSet getColumnPrivileges(
        String catalog,
        String schema,
        String table,
        String columnNamePattern)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' TABLE_CAT, "
            + "table_owner TABLE_SCHEM, "
            + "TABLE_NAME, "
            + "COLUMN_NAME, "
            + "GRANTOR, "
            + "GRANTEE, "
            + "PRIVILEGE, "
            + "IS_GRANTABLE "
            + "FROM domain.columnprivileges WHERE ");
        cmd.append ("table_name = '" + table + "' ");
        if (this.realQualification (schema)) {
            cmd.append ("AND table_owner = '" + schema + "' ");
        }
        if (this.realPatternQualification (columnNamePattern)) {
            cmd.append ("AND column_name LIKE '" + columnNamePattern + "' ESCAPE '\\' ");
        }
        cmd.append (" ORDER BY column_name, privilege ");
        return this.internalQuery (cmd.toString (), "getColumnPrivileges");
    }
    /**
     * getColumns method comment.
     */
    public ResultSet getColumns(
        String catalog,
        String schemaPattern,
        String tableNamePattern,
        String columnNamePattern)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();
        StringBuffer qualification = new StringBuffer ();
        String nullableString = noNullableColumns ?
            "decode(mode, 'OPT', 1, 0) NULLABLE," :
            "decode(NULLABLE, 'YES', 1, 0) NULLABLE,";

        cmd.append ("SELECT "
                    + "'"+defaultCatalogName+"' TABLE_CAT, "
                    + "owner TABLE_SCHEM, "
                    + "tablename TABLE_NAME, "
                    + "columnname COLUMN_NAME, "
                    + "decode (((decode (datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype))), "
                    + typename2odbc_C + ") DATA_TYPE, "
                    + "(decode(datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype)) TYPE_NAME, "
                    + "len COLUMN_SIZE, "
                    + "NULL BUFFER_LENGTH, "
                    + "dec DECIMAL_DIGITS, "
                    + "10 NUM_PREC_RADIX, "
                    + nullableString
                    + "comment REMARKS, "
                    + "\"DEFAULT\" COLUMN_DEF, "
                    + "NULL SQL_DATA_TYPE, "
                    + "NULL SQL_DATETIME_SUB, "
                    + "len CHAR_OCTET_LENGTH, ");
            if (this.connection.isSQLModeOracle)
              cmd.append( "ROWNUM ORDINAL_POSITION, ");
            else
              cmd.append( "ROWNO ORDINAL_POSITION, ");

            if(noNullableColumns) {
                cmd.append( "decode (mode, 'OPT', 'YES', 'NO') IS_NULLABLE, ");
            } else {
                cmd.append("NULLABLE IS_NULLABLE,  ");
            }
            cmd.append("NULL                       AS SCOPE_CATLOG,"
                      +"NULL                       AS SCOPE_SCHEMA,"
                      +"NULL                       AS SCOPE_TABLE,"
                      +"NULL                       AS SOURCE_DATA_TYPE");
            if ( this.dbVersionInfo.getMajorVersion() *10000 
                 + this.dbVersionInfo.getMinorVersion() *100
                 + this.dbVersionInfo.getMinorMinorVersion() >= 70403){          
                     cmd.append(" ,DEFAULTFUNCTION            AS COLUMN_DEF_FUNC ");
            }          
            cmd.append(" FROM DOMAIN.COLUMNS ");

        if (this.realPatternQualification (schemaPattern)) {
            qualification.append ("AND owner LIKE '" + schemaPattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification (tableNamePattern)) {
            qualification.append ("AND tablename LIKE '" + tableNamePattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification (columnNamePattern)) {
            qualification.append ("AND columnname LIKE '" + columnNamePattern + "' ESCAPE '\\' ");
        }
        if (qualification.length () > 0) {
            cmd.append (" WHERE 1 = 1 ");
            cmd.append (qualification.toString ());
        }
        cmd.append (" ORDER BY owner, tablename, pos ");
        try {
            return this.internalQuery (cmd.toString (), "getColumns");
        } catch(SQLException sqlEx) {
            if(sqlEx.getErrorCode() == -4005 && !this.noNullableColumns) { // NULLABLE column not found
                this.noNullableColumns = true;
                // try again without
                return getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern);
            }
            throw sqlEx;
        }
    }

    /**
     * getCrossReference method comment.
     */
    public ResultSet getCrossReference(
        String primaryCatalog,
        String primarySchema,
        String primaryTable,
        String foreignCatalog,
        String foreignSchema,
        String foreignTable)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' PKTABLE_CAT, "
            + "PKTABLE_OWNER PKTABLE_SCHEM, "
            + "PKTABLE_NAME, "
            + "PKCOLUMN_NAME, "
            + "'"+defaultCatalogName+"' FKTABLE_CAT, "
            + "FKTABLE_OWNER FKTABLE_SCHEM, "
            + "FKTABLE_NAME, "
            + "FKCOLUMN_NAME, "
            + "KEY_SEQ, "
            + DatabaseMetaData.importedKeyRestrict+" UPDATE_RULE,"
            + "DELETE_RULE, "
            + "FK_NAME, "
            + "PK_NAME, "
            + DatabaseMetaData.importedKeyNotDeferrable+" DEFERRABILITY "   // importedKeyNotDeferrable
            + "FROM sysodbcforeignkeys ");
        cmd.append ("WHERE pktable_name = '" + primaryTable + "' ");
        cmd.append ("AND fktable_name = '" + foreignTable + "' ");
        if (this.realQualification(primarySchema)) {
            cmd.append ("AND PKTABLE_OWNER = '" + primarySchema + "' ");
        }
        if (this.realQualification(foreignSchema)) {
            cmd.append ("AND FKTABLE_OWNER = '" + foreignSchema + "' ");
        }
        String fullCmd = cmd.toString ();
        return this.internalQuery (fullCmd, "getCrossReference");
    }

    /**
     * getDatabaseProductVersion method comment.
     */
    public String getDatabaseProductVersion() throws SQLException {
        if (this.dbVersion != null) {
            return this.dbVersion;
        }
        String result;

        ResultSet resultSet = this.internalQuery (
            "Select kernel from domain.versions", "getDatabaseProductVersion");
        resultSet.next ();
        result = resultSet.getString (1);
        this.dbVersion = result;
        resultSet.close ();
        return result;
    }

    /**
     * getDriverMajorVersion method comment.
     */
    public int getDriverMajorVersion() {
        return DriverSapDB.singleton ().getMajorVersion ();
    }
    /**
     * getDriverMinorVersion method comment.
     */
    public int getDriverMinorVersion() {
        return DriverSapDB.singleton ().getMinorVersion ();
    }
    /**
     * getDriverName method comment.
     */
    public String getDriverName() throws SQLException {
        return  DriverSapDB.singleton ().getName ();
    }
    /**
     * getDriverVersion method comment.
     */
    public String getDriverVersion() throws SQLException {
        return  DriverSapDB.singleton ().getVersionString ();
    }
    /**
     * getExportedKeys method comment.
     */
    public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' PKTABLE_CAT, "
            + "PKTABLE_OWNER PKTABLE_SCHEM, "
            + "PKTABLE_NAME, "
            + "PKCOLUMN_NAME, "
            + "'"+defaultCatalogName+"' FKTABLE_CAT, "
            + "FKTABLE_OWNER FKTABLE_SCHEM, "
            + "FKTABLE_NAME, "
            + "FKCOLUMN_NAME, "
            + "KEY_SEQ, "
            + DatabaseMetaData.importedKeyRestrict+" UPDATE_RULE,"
            + "DELETE_RULE, "
            + "FK_NAME, "
            + "PK_NAME, "
            + DatabaseMetaData.importedKeyNotDeferrable+" DEFERRABILITY "   // importedKeyNotDeferrable
            + "FROM sysodbcforeignkeys ");
        cmd.append ("WHERE pktable_name = '" + table + "' ");
        if (this.realQualification(schema)) {
            cmd.append ("AND PKTABLE_OWNER = '" + schema + "' ");
        }
        return this.internalQuery (cmd.toString (), "getExportedKeys");
    }

    /**
     * getImportedKeys method comment.
     */
    public ResultSet getImportedKeys(
        String catalog,
        String schema,
        String table)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' PKTABLE_CAT, "
            + "PKTABLE_OWNER PKTABLE_SCHEM, "
            + "PKTABLE_NAME, "
            + "PKCOLUMN_NAME, "
            + "'"+defaultCatalogName+"' FKTABLE_CAT, "
            + "FKTABLE_OWNER FKTABLE_SCHEM, "
            + "FKTABLE_NAME, "
            + "FKCOLUMN_NAME, "
            + "KEY_SEQ, "
            + DatabaseMetaData.importedKeyRestrict+" UPDATE_RULE,"
            + "DELETE_RULE, "
            + "FK_NAME, "
            + "PK_NAME, "
            + DatabaseMetaData.importedKeyNotDeferrable+" DEFERRABILITY "   // importedKeyNotDeferrable
//            + "COMMENT "
            + "FROM sysodbcforeignkeys ");
        cmd.append ("WHERE fktable_name = '" + table + "' ");
        if (this.realQualification(schema)) {
            cmd.append ("AND FKTABLE_OWNER = '" + schema + "' ");
        }
        return this.internalQuery (cmd.toString (), "getImportedKeys");
    }
    /**
     * getIndexInfo method comment.
     */
    public ResultSet getIndexInfo(
        String catalog,
        String schema,
        String table,
        boolean unique,
        boolean approximate)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' TABLE_CAT, "
            + "table_owner TABLE_SCHEM, "
            + "TABLE_NAME, "
            + "decode (non_unique, 1, 'true', 'false') NON_UNIQUE, "
            + "INDEX_QUALIFIER, "
            + "INDEX_NAME, "
            + "TYPE, "
            + "seq_in_index ORDINAL_POSITION, "
            + "COLUMN_NAME, "
            + "collation ASC_OR_DESC, "
            + "CARDINALITY, "
            + "PAGES, "
            + "FILTER_CONDITION "
            + "FROM sysodbcindexes ");
        cmd.append ("WHERE INDEX_NAME <> 'SYSPRIMARYKEYINDEX' ");
        cmd.append ("AND table_name = '" + table + "' ");
        if (this.realQualification (schema)) {
            cmd.append ("AND table_owner = '" + schema + "' ");
        }
        if (unique) {
            cmd.append ("AND non_unique = 0 ");
        }
        return this.internalQuery (cmd.toString (), "getIndexInfo");
    }
    /**
     * getMaxBinaryLiteralLength method comment.
     */
    public int getMaxBinaryLiteralLength() throws SQLException {
        return 8000;
    }
    /**
     * getMaxCatalogNameLength method comment.
     */
    public int getMaxCatalogNameLength() throws SQLException {
        return 0;
    }
    /**
     * getMaxCharLiteralLength method comment.
     */
    public int getMaxCharLiteralLength() throws SQLException {
        return 4000;
    }
    /**
     * getMaxColumnNameLength method comment.
     */
    public int getMaxColumnNameLength() throws SQLException {
        return 32;
    }
    /**
     * getMaxColumnsInGroupBy method comment.
     */
    public int getMaxColumnsInGroupBy() throws SQLException {
        return 128;
    }
    /**
     * getMaxColumnsInIndex method comment.
     */
    public int getMaxColumnsInIndex() throws SQLException {
        return 16;
    }
    /**
     * getMaxColumnsInOrderBy method comment.
     */
    public int getMaxColumnsInOrderBy() throws SQLException {
        return 128;
    }
    /**
     * getMaxColumnsInSelect method comment.
     */
    public int getMaxColumnsInSelect() throws SQLException {
        return 1023;
    }
    /**
     * getMaxColumnsInTable method comment.
     */
    public int getMaxColumnsInTable() throws SQLException {
        return 1023;
    }
    /**
     * getMaxConnections method comment.
     */
    public int getMaxConnections() throws SQLException {
      try {
          ResultSet rs = this.internalQuery(
            "select value from dbparameters "
           +"   where description = 'MAXUSERTASKS'",
           "getMaxConnections"
          );
        rs.next();
        int max = rs.getInt(1);
        rs.close();
        return max;
      }
      catch (SQLException ex) {
        return 0;
      }
    }
    /**
     * getMaxCursorNameLength method comment.
     */
    public int getMaxCursorNameLength() throws SQLException {
        return 32;
    }
    /**
     * getMaxIndexLength method comment.
     */
    public int getMaxIndexLength() throws SQLException {
        return 1024;
    }
    /**
     * getMaxProcedureNameLength method comment.
     */
    public int getMaxProcedureNameLength() throws SQLException {
        return 32;
    }
    /**
     * getMaxRowSize method comment.
     */
    public int getMaxRowSize() throws SQLException {
        return 8088;
    }
    /**
     * getMaxSchemaNameLength method comment.
     */
    public int getMaxSchemaNameLength() throws SQLException {
        return 32;
    }
    /**
     * getMaxStatementLength method comment.
     */
    public int getMaxStatementLength() throws SQLException {
        return this.connection.maxStatementLength ();
    }
    /**
     * getMaxStatements method comment.
     */
    public int getMaxStatements() throws SQLException {
        return Integer.MAX_VALUE;
    }
    /**
     * getMaxTableNameLength method comment.
     */
    public int getMaxTableNameLength() throws SQLException {
        return 32;
    }
    /**
     * getMaxTablesInSelect method comment.
     */
    public int getMaxTablesInSelect() throws SQLException {
        return 64;
    }
    /**
     * getMaxUserNameLength method comment.
     */
    public int getMaxUserNameLength() throws SQLException {
        return 32;
    }

    /**
     * getPrimaryKeys method comment.
     */
    public ResultSet getPrimaryKeys(
        String catalog,
        String schema,
        String table)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' TABLE_CAT, "
            + "owner TABLE_SCHEM , "
            + "tablename TABLE_NAME, "
            + "columnname COLUMN_NAME, "
            + "keypos KEY_SEQ, "
            + "null PK_NAME "
            + "FROM domain.columns ");
        cmd.append ("WHERE tablename = '" + table + "' ");
        if (this.realQualification(schema)) {
            cmd.append ("AND owner = '" + schema + "' ");
        }
        cmd.append ("AND keypos is not null ");
        cmd.append (" ORDER BY column_name ");
        return this.internalQuery (cmd.toString (), "getPrimaryKeys");
    }
    /**
     * getProcedureColumns method comment.
     */
    public ResultSet getProcedureColumns(
        String catalog,
        String schemaPattern,
        String procedureNamePattern,
        String columnNamePattern)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();
        StringBuffer qualification = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' PROCEDURE_CAT, "
            + "owner PROCEDURE_SCHEM, "
            + "dbprocname PROCEDURE_NAME, "
            + "parametername COLUMN_NAME, "
            + "decode (\"IN/OUT-TYPE\", 'IN', 1, '   OUT', 2, 'IN/OUT', 4, 0) COLUMN_TYPE, "
            + "decode (((decode (datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype))), "
            + typename2odbc_C + ") DATA_TYPE, "
            + "(decode(datatype,"+DataTypeSuffix_C+"))|| (' ' || (codetype)) TYPE_NAME, "
            + "len PRECISION, "
            + "len \"LENGTH\", "
            + "dec SCALE, "
            + "10 RADIX, "
            + "2 NULLABLE, "
            + "NULL REMARKS "
            + "FROM domain.dbprocparams ");
        if (this.realPatternQualification(schemaPattern)) {
            qualification.append ("AND owner like '" + schemaPattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification(procedureNamePattern)) {
            qualification.append ("AND dbprocname like '" + procedureNamePattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification(columnNamePattern)) {
            qualification.append ("AND parametername like '" + columnNamePattern + "' ESCAPE '\\' ");
        }
        if (qualification.length () > 0) {
            cmd.append ("WHERE TRUE ");
            cmd.append (qualification.toString ());
        }
        cmd.append ("ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME, pos ");
        return this.internalQuery (cmd.toString (), "getProcedureColumns");
    }
    /**
     * getProcedures method comment.
     */
    public ResultSet getProcedures(
        String catalog,
        String schemaPattern,
        String procedureNamePattern)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();
        StringBuffer qualification = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' PROCEDURE_CAT, "
            + "owner PROCEDURE_SCHEM, "
            + "dbprocname PROCEDURE_NAME, "
            + "NULL RESERVED4, "
            + "NULL RESERVED5, "
            + "NULL RESERVED6, "
            + "comment REMARKS, "
            + "1 PROCEDURE_TYPE "
            + "FROM domain.DBPROCEDURES ");
        if (this.realPatternQualification(schemaPattern)) {
            qualification.append ("AND owner like '" + schemaPattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification(procedureNamePattern)) {
            qualification.append ("AND dbprocname like '" + procedureNamePattern + "' ESCAPE '\\' ");
        }
        if (qualification.length () > 0) {
            cmd.append ("WHERE 1 = 1 ");
            cmd.append (qualification.toString ());
        }
        return this.internalQuery (cmd.toString (), "getProcedures");
    }

    /**
     * getSchemas method comment.
     */
    public ResultSet getSchemas() throws SQLException {
        return this.internalQuery ("Select username TABLE_SCHEM from domain.users  WHERE USERMODE <> 'COLDUSER' order by TABLE_SCHEM", "getSchemas");
    }

    /**
     * getTablePrivileges method comment.
     */
    public ResultSet getTablePrivileges(
        String catalog,
        String schemaPattern,
        String tableNamePattern)
            throws SQLException
    {
        StringBuffer cmd = new StringBuffer ();
        StringBuffer qualification = new StringBuffer ();

        cmd.append ("SELECT "
            + "'"+defaultCatalogName+"' TABLE_CAT, "
            + "OWNER TABLE_SCHEM, "
            + "TABLENAME TABLE_NAME, "
            + "GRANTOR, "
            + "GRANTEE, "
            + "PRIVILEGE, "
            + "IS_GRANTABLE "
            + "FROM domain.tableprivileges ");
        if (this.realPatternQualification(schemaPattern)) {
            qualification.append ("AND owner LIKE '" + schemaPattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification(tableNamePattern)) {
            qualification.append ("AND tablename LIKE '" + tableNamePattern + "' ESCAPE '\\' ");
        }
        if (qualification.length () > 0) {
            cmd.append ("WHERE 1 = 1 ");
            cmd.append (qualification.toString ());
        }
        cmd.append (" ORDER BY owner, tablename ");
        return this.internalQuery (cmd.toString (), "getTablePrivileges");
    }
    /**
     * getTables method comment.
     */
    public ResultSet
    getTables(
        String catalog,
        String schemaPattern,
        String tableNamePattern,
        java.lang.String[] types)
    throws
        SQLException
    {
        StringBuffer buf = new StringBuffer ();

        buf.append ("SELECT '"+defaultCatalogName+"' TABLE_CAT, owner TABLE_SCHEM, ");
        buf.append ("tablename TABLE_NAME, type TABLE_TYPE, ");
        buf.append ("comment REMARKS, SAMPLE_PERCENT, SAMPLE_ROWS from domain.tables WHERE 1 = 1 ");
        if (this.realPatternQualification (schemaPattern)) {
            buf.append ("AND owner LIKE '" + schemaPattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification (tableNamePattern)) {
            buf.append ("AND tablename LIKE '" + tableNamePattern + "' ESCAPE '\\' ");
        }
        if (types == null) {
            buf.append ("AND type <> 'RESULT' ");
        }
        else if ((types != null) && (types.length > 0)) {
            buf.append ("AND type in ('" + types [0] + "'");
            for (int i = 1; i < types.length; ++i) {
                buf.append (",'" + types [i] + "'");
            }
            buf.append (") ");
        }
        buf.append ("ORDER BY TABLE_TYPE, TABLE_SCHEM, TABLE_NAME");
        return this.internalQuery (buf.toString (), "getTables");
    }
    /**
     * getTableTypes method comment.
     */
    public ResultSet getTableTypes() throws SQLException {
        ResultSet result;
        String [] colHeadings = {"TABLE_TYPE"};
        Object [][] rows = {
            {"RESULT"},
            {"SYNONYM"},
            {"SYSTEM"},
            {"TABLE"},
            {"VIEW"},
            };

        result = new MemoryResultSetSapDB (colHeadings, rows);
        return result;
    }

    /**
     * getTypeInfo method comment.
     */
    public ResultSet getTypeInfo() throws SQLException {
        // to do: cache result in a memory resultset
        StringBuffer cmd = new StringBuffer ();
        cmd.append ("SELECT "
            + "TYPE_NAME, "
            + "decode (TYPE_NAME, " + typename2odbc_C + ") DATA_TYPE, "
            + "PRECISION, "                 //!!! COLUMN_SIZE
            + "LITERAL_PREFIX, "
            + "LITERAL_SUFFIX, "
            + "CREATE_PARAMS, "
            + "NULLABLE, "
            + "CASE_SENSITIVE, "
            + "SEARCHABLE, "
            + "UNSIGNED_ATTRIBUTE, "
            + "money FIXED_PREC_SCALE , "   //!!! FIXED_PREC_SCALE
            + "AUTO_INCREMENT, "             //!!! AUTO_UNIQUE_VALUE
            + "LOCAL_TYPE_NAME, "
            + "MINIMUM_SCALE, "
            + "MAXIMUM_SCALE, "
            + "NULL SQL_DATA_TYPE , "       //!!! definiert
            + "NULL SQL_DATETIME_SUB , "    //!!! definiert
            + "10 NUM_PREC_RADIX "
                                             //!!! INTERVAL_PRECISION
            + "FROM sysodbctypes ");
            if (this.connection.isSQLModeOracle){
              cmd.append("WHERE TYPE_NAME NOT IN ('TIMESTAMP','TIME','BOOLEAN','FIXED') ");
            }
            cmd.append("ORDER BY DATA_TYPE ");

        return this.internalQuery (cmd.toString (), "getTypeInfo");
    }
    /**
     * JDBC 2.0
     *
     * Gets a description of the user-defined types defined in a particular
     * schema.  Schema-specific UDTs may have type JAVA_OBJECT, STRUCT,
     * or DISTINCT.
     *
     * <P>Only types matching the catalog, schema, type name and type
     * criteria are returned.  They are ordered by DATA_TYPE, TYPE_SCHEM
     * and TYPE_NAME.  The type name parameter may be a fully-qualified
     * name.  In this case, the catalog and schemaPattern parameters are
     * ignored.
     *
     * <P>Each type description has the following columns:
     *  <OL>
     *  <LI><B>TYPE_CAT</B> String => the type's catalog (may be null)
     *  <LI><B>TYPE_SCHEM</B> String => type's schema (may be null)
     *  <LI><B>TYPE_NAME</B> String => type name
     *  <LI><B>CLASS_NAME</B> String => Java class name
     *  <LI><B>DATA_TYPE</B> String => type value defined in java.sql.Types.
     *  One of JAVA_OBJECT, STRUCT, or DISTINCT
     *  <LI><B>REMARKS</B> String => explanatory comment on the type
     *  </OL>
     *
     * <P><B>Note:</B> If the driver does not support UDTs, an empty
     * result set is returned.
     *
     * @param catalog a catalog name; "" retrieves those without a
     * catalog; null means drop catalog name from the selection criteria
     * @param schemaPattern a schema name pattern; "" retrieves those
     * without a schema
     * @param typeNamePattern a type name pattern; may be a fully-qualified
     * name
     * @param types a list of user-named types to include (JAVA_OBJECT,
     * STRUCT, or DISTINCT); null returns all types
     * @return ResultSet - each row is a type description
     * @exception SQLException if a database access error occurs
     */
    public java.sql.ResultSet getUDTs(String catalog, String schemaPattern,
                  String typeNamePattern, int[] types)
      throws SQLException {
        return new MemoryResultSetSapDB(new String []{"TYPE_CAT",
                                         "TYPE_SCHEM",
                                         "TYPE_NAME",
                                         "CLASS_NAME",
                                         "DATA_TYPE",
                                         "REMARKS" ,
                                         "BASE_TYPE"},
                                         new Object [] [] {} );
    }

    /**
     * getVersionColumns method comment.
     */
    public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
      return new MemoryResultSetSapDB(new String []{"SCOPE",
                                         "COLUMN_NAME",
                                         "DATA_TYPE",
                                         "TYPE_NAME",
                                         "COLUMN_SIZE",
                                         "BUFFER_LENGTH" ,
                                         "DECIMAL_DIGITS" ,
                                         "PSEUDO_COLUMN"},
                                         new Object [] [] {} );
    }

    /**
     *
     * @return boolean
     * @param pattern java.lang.String
     */
    private boolean realPatternQualification (String pattern) {
        if (pattern == null) {
            return false;
        }
        if (pattern.length () == 0) {
            return false;
        }
        if (pattern.equals ("*")) {
            return false;
        }
        return true;
    }
    /**
     *
     * @return boolean
     * @param pattern java.lang.String
     */
    private boolean realQualification (String pattern) {
        if (pattern == null) {
            return false;
        }
        if (pattern.length () == 0) {
            return false;
        }
        return true;
    }

    /**
   * 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.
   *
   * 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.
   *
   * 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 object in which a row gives information
   * about the designated UDT
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4
   *
   */
  public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws java.sql.SQLException {
          return new MemoryResultSetSapDB(new String []{"TYPE_CAT",
                                                        "TYPE_SCHEM",
                                                        "TYPE_NAME",
                                                        "SUPERTYPE_CAT",
                                                        "SUPERTYPE_SCHEM",
                                                        "SUPERTYPE_NAME"},
                                                        new Object [] [] {} );
  }

  /**
   * 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.
   * </P>
   * @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 java.sql.SQLException - if a database access error occurs
   * @since 1.4
   *
   */
  public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws java.sql.SQLException {
            return new MemoryResultSetSapDB(new String []{"TABLE_CAT",
                                                        "TABLE_SCHEM",
                                                        "TABLE_NAME",
                                                        "SUPERTABLE_NAME"},
                                                        new Object [] [] {}  );
  }
  /**
   * 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
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4 JDBC 3.0
   *
   */
  public ResultSet getAttributes(String catalog,
                               String schemaPattern,
                               String typeNamePattern,
                               String attributeNamePattern) throws java.sql.SQLException {
          return new MemoryResultSetSapDB(new String []{"TYPE_CAT",
                                                        "TYPE_SCHEM",
                                                        "TYPE_NAME",
                                                        "ATTR_NAME",
                                                        "DATA_TYPE",
                                                        "ATTR_TYPE_NAME",
                                                        "ATTR_SIZE",
                                                        "DECIMAL_DIGITS",
                                                        "NUM_PREC_RADIX",
                                                        "NULLABLE",
                                                        "REMARKS",
                                                        "ATTR_DEF",
                                                        "SQL_DATA_TYPE",
                                                        "SQL_DATETIME_SUB",
                                                        "CHAR_OCTET_LENGTH",
                                                        "ORDINAL_POSITION",
                                                        "IS_NULLABLE",
                                                        "SCOPE_CATALOG",
                                                        "SCOPE_SCHEMA",
                                                        "SCOPE_TABLE",
                                                        "SOURCE_DATA_TYPE"},
                                                        new Object [] [] {} );
  }

  /**
   * Retrieves the major version number of the underlying database.
   * @return the underlying database's major version
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4 JDBC 3.0
   *
   */
  public int getDatabaseMajorVersion() throws java.sql.SQLException {
     return this.dbVersionInfo.getMajorVersion();
  }
  /**
   * Retrieves the minor version number of the underlying database.
   * @return underlying database's minor version
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4 JDBC 3.0
   *
   */
  public int getDatabaseMinorVersion() throws java.sql.SQLException {
     return this.dbVersionInfo.getMinorVersion();
  }
  /**
   * Retrieves the major JDBC version number for this
   *  driver.
   * @return JDBC version major number
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4 JDBC 3.0
   *
   */
  public int getJDBCMajorVersion() throws java.sql.SQLException {
     return 3;
  }
  /**
   * Retrieves the minor JDBC version number for this
   *  driver.
   * @return JDBC version minor number
   * @throws java.sql.SQLException - if a database access error occurs
   * @since 1.4 JDBC 3.0
   *
   */
  public int getJDBCMinorVersion() throws java.sql.SQLException {
     return 0;
  }

      /**
     * getTables method comment.
     */
    public ResultSet getConstraints(
                String catalog,
                String schemapattern,
                String tablepattern)
                throws SQLException
    {
        StringBuffer buf = new StringBuffer ();

        buf.append ("SELECT '"+defaultCatalogName+"' TABLE_CAT, owner TABLE_SCHEM, "
                  + "tablename TABLE_NAME, CONSTRAINTNAME AS CONSTRAINT_NAME,"
                  + "DEFINITION AS CONSTRAINTS_DEFINITION ");
        buf.append ("FROM DOMAIN.CONSTRAINTS WHERE 1 = 1 ");
        if (this.realPatternQualification (schemapattern)) {
            buf.append ("AND owner LIKE '" + schemapattern + "' ESCAPE '\\' ");
        }
        if (this.realPatternQualification (tablepattern)) {
            buf.append ("AND tablename LIKE '" + tablepattern + "' ESCAPE '\\' ");
        }
        buf.append ("ORDER BY TABLE_TYPE, TABLE_SCHEM, TABLE_NAME");
        return this.internalQuery (buf.toString (), "getConstraints");
    }
}
