/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.queries;

import java.util.*;
import org.eclipse.persistence.internal.expressions.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.sessions.SessionProfiler;
/**
 * <p><b>Purpose</b>:
 * Mechanism used for all  statement objects.
 * <p>
 * <p><b>Responsibilities</b>:
 * Executes the appropriate statement.
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public class StatementQueryMechanism extends CallQueryMechanism {
    protected SQLStatement sqlStatement;

    /** Normally only a single statement is used, however multiple table may require multiple statements on write. */
    protected Vector sqlStatements;

    public StatementQueryMechanism() {
    }

    /**
     * INTERNAL:
     * Return a new mechanism for the query
     * @param query - owner of mechanism
     */
    public StatementQueryMechanism(DatabaseQuery query) {
        super(query);
    }

    /**
     * Return a new mechanism for the query
     * @param query - owner of mechanism
     * @param statement - sql statement
     */
    public StatementQueryMechanism(DatabaseQuery query, SQLStatement statement) {
        super(query);
        this.sqlStatement = statement;
    }

    /**
     * The statement is no longer require after prepare so can be released.
     */
    public void clearStatement() {
        // Only clear the statement if it is an expression query, otherwise the statement may still be needed.
    }

    /**
     * Clone the mechanism for the specified query clone.
     */
    public DatabaseQueryMechanism clone(DatabaseQuery queryClone) {
        StatementQueryMechanism clone = (StatementQueryMechanism)super.clone(queryClone);
        if ((!hasMultipleStatements()) && (getSQLStatement() != null)) {
            clone.setSQLStatement((SQLStatement)sqlStatement.clone());
        } else {
            Vector currentStatements = getSQLStatements();
            if (currentStatements != null) {
                Vector statementClone = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(currentStatements.size());
                Enumeration enumtr = currentStatements.elements();
                while (enumtr.hasMoreElements()) {
                    statementClone.addElement(((SQLStatement)enumtr.nextElement()).clone());
                }
                clone.setSQLStatements(statementClone);
            }
        }
        return clone;
    }

    /**
     * INTERNAL:
     * delete the object
     * @exception  DatabaseException - an error has occurred on the database.
     * @return the row count.
     */
    public Integer deleteObject() throws DatabaseException {
        // Prepare the calls if not already set (prepare may not have had the modify row).
        if ((this.call == null) && (!hasMultipleCalls())) {
            prepareDeleteObject();
            if ((this.call == null) && (!hasMultipleCalls())) {
                return Integer.valueOf(1);// Must be 1 otherwise locking error will occur.
            }
        }

        return super.deleteObject();
    }

    /**
     * Update the object
     * @exception  DatabaseException - an error has occurred on the database.
     * @return the row count.
     */
    public Integer executeNoSelect() throws DatabaseException {
        // Prepare the calls if not already set (prepare may not have had the modify row).
        if ((this.call == null) && (!hasMultipleCalls())) {
            prepareExecuteNoSelect();
        }

        return super.executeNoSelect();
    }

    /**
     * Return the selection criteria for the statement.
     */
    public Expression getSelectionCriteria() {
        return getSQLStatement().getWhereClause();
    }

    /**
     * INTERNAL:
     * Return the sqlStatement
     */
    public SQLStatement getSQLStatement() {
        return sqlStatement;
    }

    /**
     * Normally only a single statement is used, however multiple table may require multiple statements on write.
     * This is lazy initialied to conserv space.
     */
    public Vector getSQLStatements() {
        if (sqlStatements == null) {
            sqlStatements = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(3);
        }
        return sqlStatements;
    }

    /**
     * Normally only a single statement is used, however multiple table may require multiple statements on write.
     * This is lazy initialized to conserve space.
     */
    public boolean hasMultipleStatements() {
        return (sqlStatements != null) && (!sqlStatements.isEmpty());
    }

    /**
     * Insert the object
     * @exception  DatabaseException - an error has occurred on the database.
     */
    public void insertObject() throws DatabaseException {
        // Prepare the calls if not already set (prepare may not have had the modify row).
        if ((this.call == null) && (!hasMultipleCalls())) {
            prepareInsertObject();
        }

        super.insertObject();
    }

    /**
     * Insert the object if the reprepare flag is set, first reprepare the query.
     * Added for CR#3237
     * @param boolean reprepare - whether to reprepare the query.
     */
    public void insertObject(boolean reprepare) {
        if (reprepare) {
            // Clear old calls, and reprepare.
            setCalls(null);
            trimFieldsForInsert();
            prepareInsertObject();
        }
        insertObject();
    }

    /**
     * INTERNAL
     * Remove a potential sequence number field and invoke the ReturningPolicy trimModifyRowsForInsert method
     */
    public void trimFieldsForInsert() {
        getDescriptor().getObjectBuilder().trimFieldsForInsert(getSession(), getModifyRow());
    }

    /**
     * Return true if this is a call query mechanism
     */
    public boolean isCallQueryMechanism() {
        return false;
    }

    /**
     * Return true if this is a statement query mechanism
     */
    public boolean isStatementQueryMechanism() {
        return true;
    }

    /**
     * INTERNAL:
     * This is different from 'prepareForExecution' in that this is called on the original query,
     * and the other is called on the copy of the query.
     * This query is copied for concurrency so this prepare can only setup things that
     * will apply to any future execution of this query.
     */
    public void prepare() {
        if ((!hasMultipleStatements()) && (getSQLStatement() == null)) {
            throw QueryException.sqlStatementNotSetProperly(getQuery());
        }

        // Cannot call super yet as the call is not built.
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareCursorSelectAllRows() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareCursorSelectAllRows();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareDeleteAll() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareDeleteAll();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareDeleteObject() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareDeleteObject();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareDoesExist(DatabaseField field) {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        getCall().returnOneRow();
        prepareCall();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareExecuteNoSelect() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareExecuteNoSelect();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareExecuteSelect() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareExecuteSelect();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareInsertObject() {
        // Require modify row to prepare.
        if (getModifyRow() == null) {
            return;
        }

        if (hasMultipleStatements()) {
            for (Enumeration statementEnum = getSQLStatements().elements();
                     statementEnum.hasMoreElements();) {
                ((SQLModifyStatement)statementEnum.nextElement()).setModifyRow(getModifyRow());
            }
        } else if (getSQLStatement() != null) {
            ((SQLModifyStatement)getSQLStatement()).setModifyRow(getModifyRow());
        }
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareInsertObject();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareSelectAllRows() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareSelectAllRows();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareSelectOneRow() {
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareSelectOneRow();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareUpdateObject() {
        // Require modify row to prepare.
        if (getModifyRow() == null) {
            return;
        }

        if (hasMultipleStatements()) {
            for (Enumeration statementEnum = getSQLStatements().elements();
                     statementEnum.hasMoreElements();) {
                ((SQLModifyStatement)statementEnum.nextElement()).setModifyRow(getModifyRow());
            }
        } else if (getSQLStatement() != null) {
            ((SQLModifyStatement)getSQLStatement()).setModifyRow(getModifyRow());
        }
        setCallFromStatement();
        // The statement is no longer require so can be released.
        clearStatement();

        super.prepareUpdateObject();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    public void prepareUpdateAll() {
        setCallFromStatement();// Will build an SQLUpdateAllStatement
        clearStatement();// The statement is no longer require so can be released.
        super.prepareUpdateAll();
    }

    /**
     * Pre-build the SQL call from the statement.
     */
    protected void setCallFromStatement() {
        // Profile SQL generation.
        getSession().startOperationProfile(SessionProfiler.SqlGeneration, getQuery(), SessionProfiler.ALL);
        try {
            if (hasMultipleStatements()) {
                for (Enumeration statementEnum = getSQLStatements().elements(); statementEnum.hasMoreElements();) {
                    DatasourceCall call = null;
                    if (getDescriptor() != null) {
                        call = getDescriptor().buildCallFromStatement((SQLStatement)statementEnum.nextElement(), getQuery(), getExecutionSession());
                    } else {
                        call = ((SQLStatement)statementEnum.nextElement()).buildCall(getExecutionSession());
                    }

                    // In case of update call may be null if no update required.
                    if (call != null) {
                        addCall(call);
                    }
                }
            } else {
                DatasourceCall call = null;
                if (getDescriptor() != null) {
                    call = getDescriptor().buildCallFromStatement(getSQLStatement(), getQuery(), getExecutionSession());
                } else {
                    call = getSQLStatement().buildCall(getExecutionSession());
                }

                // In case of update call may be null if no update required.
                if (call != null) {
                    setCall(call);
                }
            }
        } finally {
            // Profile SQL generation.
            getSession().endOperationProfile(SessionProfiler.SqlGeneration, getQuery(), SessionProfiler.ALL);
        }
    }

    /**
     * Set the sqlStatement
     */
    public void setSQLStatement(SQLStatement statement) {
        this.sqlStatement = statement;
    }

    /**
     * Normally only a single statement is used, however multiple table may require multiple statements on write.
     * This is lazy initialized to conserve space.
     */
    protected void setSQLStatements(Vector sqlStatements) {
        this.sqlStatements = sqlStatements;
    }

    /**
     * Update the object
     * @exception  DatabaseException - an error has occurred on the database.
     * @return the row count.
     */
    public Integer updateObject() throws DatabaseException {
        // Prepare the calls if not already set (prepare may not have had the modify row).
        if ((this.call == null) && (!hasMultipleCalls())) {
            prepareUpdateObject();
            if ((this.call == null) && (!hasMultipleCalls())) {
                return Integer.valueOf(1);// Must be 1 otherwise locking error will occur.
            }
        }

        return super.updateObject();
    }
}
