/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.empire.dbms;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;

import org.apache.empire.data.DataType;
import org.apache.empire.db.DBColumnExpr;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBCommandExpr;
import org.apache.empire.db.DBDDLGenerator.DDLActionType;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBObject;
import org.apache.empire.db.DBRelation;
import org.apache.empire.db.DBSQLBuilder;
import org.apache.empire.db.DBSQLScript;
import org.apache.empire.db.DBTableColumn;
import org.apache.empire.db.validation.DBModelChecker;
import org.apache.empire.db.validation.DBModelParser;

/**
 * The DBMSHandler interface implements all RDBMS specific logic
 */
public interface DBMSHandler
{
    /**
     * Checks if a database exists
     * @param db the database
     * @param conn the Jdbc connection
     * @return true if database exists of false otherwise
     */
    boolean checkExists(DBDatabase db, Connection conn);
    
    /**
     * Called when a database is opened
     * @param db the database
     * @param conn the Jdbc connection
     */
    void attachDatabase(DBDatabase db, Connection conn);

    /**
     * Called when a database is closed
     * @param db the database
     * @param conn the Jdbc connection
     */
    void detachDatabase(DBDatabase db, Connection conn);
    
    /**
     * This function creates a DBSQLBuilder for this DBMS
     * @return a DBMS specific DBSQLBuilder object
     */
    DBSQLBuilder createSQLBuilder();

    /**
     * This function creates a DBCommand for this DBMS
     * @param autoPrepareStmt whether or not the Command should automatically generate a prepared statement (using ?)
     * @return a DBMS specific DBCommand object
     */
    DBCommand createCommand(boolean autoPrepareStmt);

    /**
     * This function gives the dbms a chance to provide a custom implementation 
     * for a combined command such as UNION or INTERSECT 
     * @param left the left command
     * @param keyWord the key word (either "UNION" or "INTERSECT")
     * @param right the right command
     * @return a DBCommandExpr object
     */
    DBCommandExpr createCombinedCommand(DBCommandExpr left, String keyWord, DBCommandExpr right);

    /**
     * Returns whether or not a particular feature is supported by this dbms
     * @param type type of requested feature. @see DBMSFeature
     * @return true if the features is supported or false otherwise
     */
    boolean isSupported(DBMSFeature type);

    /**
     * Returns true if a name needs to be wrapped in quotes or false otherwise
     * @param name the name of the object (table, view or column)
     */
    public boolean detectQuoteName(DBObject object, String name);
    
    /**
     * Appends a table, view or column name to an SQL phrase. 
     * 
     * @param sql the StringBuilder containing the SQL phrase.
     * @param name the name of the object (table, view or column)
     * @param useQuotes use quotes or not. When null is passed then detectQuoteName() is called
     */
    void appendObjectName(DBSQLBuilder sql, String name, Boolean useQuotes);
    
    /**
     * Returns an sql phrase template for this database system.<br>
     * Templates for sql function expressions must contain a '?' character which will be 
     * replaced by the current column expression.<br>
     * If other parameters are necessary the template must contain placeholders like {0}, {1} etc. 
     * @param phrase the identifier of the phrase  
     * @return the phrase template
     */
    String getSQLPhrase(DBSqlPhrase phrase);

    /**
     * Returns a data type convertion phrase template for this dbms<br>
     * The returned template must contain a '?' which will be replaced by a column expression.
     * @param destType the target data type
     * @param srcType the source data type
     * @param format additional formatting information (optional) 
     * @return the data conversion phrase template
     */
    String getConvertPhrase(DataType destType, DataType srcType, Object format);

    /**
     * Returns a DMBS-Timestamp that is used for record updates.
     * @param conn the connection that might be used 
     * @return the current date and time.
     */
    Timestamp getUpdateTimestamp(Connection conn);
    
    /**
     * Returns an auto-generated value for a particular column
     * 
     * @param db the database
     * @param column the column for which a value is required
     * @param conn a valid database connection
     * @return the auto-generated value
     */
    Object getColumnAutoValue(DBDatabase db, DBTableColumn column, Connection conn);

    /**
     * Returns an expression that ignores the case of a column expression
     * This is only called for case sensitive column expressions (expr.isCaseSensitive())
     * Default is upper(expr)
     * @param columnExpr the expression for which to ignore the case
     * @return the ignore case expression
     */
    DBColumnExpr getIgnoreCaseExpr(DBColumnExpr columnExpr);
    
    /**
     * Executes an select SQL-command that returns only one scalar value
     * if no row are returned by the query then ObjectUtils.NO_VALUE is returned
     * 
     * @param sqlCmd the SQL-Command
     * @param sqlParams array of sql command parameters used for prepared statements (Optional).
     * @param dataType the requested return type
     * @param conn a valid connection to the database.
     * @return the scalar result value or ObjectUtils.NO_VALUE if no row are returned by the query
     */
    public Object querySingleValue(String sqlCmd, Object[] sqlParams, DataType dataType, Connection conn)
        throws SQLException;
    
    /**
     * This interface is used to set the auto generated keys when executing insert statements.
     */
    public interface DBSetGenKeys
    {
        void set(int rownum, Object value);
    }
    
    /**
     * Executes an insert, update or delete SQL-command
     * 
     * @param sqlCmd the SQL-Command
     * @param sqlParams array of sql command parameters used for prepared statements (Optional).
     * @param conn a valid connection to the database.
     * @param genKeys allows to set the auto generated key of a record (INSERT statements only)
     * 
     * @return the row count for insert, update or delete or 0 for SQL statements that return nothing
     * 
     * @throws SQLException thrown if a database access error occurs
     */
    int executeSQL(String sqlCmd, Object[] sqlParams, Connection conn, DBSetGenKeys genKeys)
        throws SQLException;

    /**
     * Executes a list of sql statements as batch
     * @param sqlCmd the sql command
     * @param sqlCmdParams the command params
     * @param conn the jdbc connection
     * @return the number of affected records
     * @throws SQLException thrown if a database access error occurs
     */
    public int[] executeBatch(String[] sqlCmd, Object[][] sqlCmdParams, Connection conn)
        throws SQLException;
    
    /**
     * Executes an select SQL-command and returns the query results
     * 
     * @param sqlCmd the SQL-Command
     * @param sqlParams array of sql command parameters used for prepared statements (Optional).
     * @param scrollable true if scrollable or false otherwise
     * @param conn a valid connection to the database.
     * @return the JDBC resultset
     * @throws SQLException thrown if a database access error occurs
     */
    public ResultSet executeQuery(String sqlCmd, Object[] sqlParams, boolean scrollable, Connection conn)
        throws SQLException;

    /**
     * Reads a single column value from the given JDBC ResultSet and returns a value object of desired data type. 
     * 
     * This gives the dbms the opportunity to change the value
     * i.e. to simulate missing data types with other types.
     * 
     * @param rset the sql Resultset with the current data row
     * @param columnIndex one based column Index of the desired column
     * @param dataType the required data type
     * 
     * @return the value of the Column 
     * 
     * @throws SQLException if a database access error occurs
     */
    Object getResultValue(ResultSet rset, int columnIndex, DataType dataType)
        throws SQLException;

    /**
     * Closes the provided JDBC Resultset
     * Use it instead of rset.close() and stmt.close() 
     * <P>
     * @param rset a ResultSet object
     */
    void closeResultSet(ResultSet rset);
    
    /**
     * Appends the required DLL commands to create, drop or alter an object to the supplied DBDQLScript.
     * @param type operation to perform (CREATE, DROP, ALTER)
     * @param dbo the object for which to perform the operation (DBDatabase, DBTable, DBView, DBColumn, DBRelation) 
     * @param script the script to which to add the DDL command(s)
     */
    void getDDLScript(DDLActionType type, DBObject dbo, DBSQLScript script);
    
    /**
     * Appends a statement to enable or disable a foreign key relation.<br>
     * The default is to drop or create the relation 
     * Override this method to provide different behavior for your database.
     * @param r the foreign key relation which should be enabled or disabled
     * @param enable true to enable the relation or false to disable
     * @param script the script to which to add the DDL command(s)
     */
    void appendEnableRelationStmt(DBRelation r, boolean enable, DBSQLScript script);
    
    /**
     * Creates a DataModelParser instance of this DBMSHandler
     * @param catalog the database catalog
     * @param schema the database schema
     * @return the model parser
     */
    DBModelParser createModelParser(String catalog, String schema);
 
    /**
     * Creates a DataModelChecker instance of this DBMSHandler
     * @param db the database
     * @return the model checker
     */
    DBModelChecker createModelChecker(DBDatabase db);
    
    /**
     * Extracts native error message of an sqlExeption.
     * 
     * @param e the SQLException
     * @return the error message of the database 
     */
    String extractErrorMessage(SQLException e);
 
}