/*
 * Copyright (c) 1998, 2021 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.testing.tests.queries.options;

import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.internal.databaseaccess.*;

import java.math.*;
import java.util.*;
import java.sql.*;
import java.lang.reflect.*;

/**
 * Test to verify that max rows, query timeout and result set fetch size are cleared
 * on PreparedStatement objects utilized by TopLink. After a query has been executed,
 * these settings must be cleared so that other queries do not use these options
 * that are set local to each query.
 * For Bug 5709179 - MAX-ROWS/TIMEOUT NOT RESET ON CACHED STATEMENTS
 * @author dminsky
 */
public class ClearQueryOptionsOnStatementTest extends AutoVerifyTestCase {

    private List employeesCreated;
    protected boolean TYPE_SCROLL_INSENSITIVE_isSupported;
    protected boolean CONCUR_UPDATABLE_isSupported;

    public ClearQueryOptionsOnStatementTest() {
        super();
        setDescription("This test verifies max rows, query timeout & result set fetch size are cleared on prepared statements");
    }

    @Override
    public void setup() {
        TYPE_SCROLL_INSENSITIVE_isSupported = true;
        CONCUR_UPDATABLE_isSupported = true;
        if(getSession().getPlatform().isSQLServer()) {
            // In case either TYPE_SCROLL_INSENSITIVE or CONCUR_UPDATABLE used
            // MS SQL Server  Version: 9.00.2050;  MS SQL Server 2005 JDBC Driver  Version: 1.2.2828.100 throws exception:
            // com.microsoft.sqlserver.jdbc.SQLServerException: The cursor type/concurrency combination is not supported.
            TYPE_SCROLL_INSENSITIVE_isSupported = false;
            CONCUR_UPDATABLE_isSupported = false;
        }
        // must enable statement caching
        getDatabaseSession().getLogin().cacheAllStatements();
        getDatabaseSession().getIdentityMapAccessor().initializeAllIdentityMaps();
        UnitOfWork uow = getDatabaseSession().acquireUnitOfWork();
        employeesCreated = new ArrayList(10);
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(190), "Jak"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(191), "Daxter"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(192), "Ratchet"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(193), "Clank"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(194), "Crash"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(195), "Sonic"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(196), "Mario"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(197), "Luigi"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(198), "Peach"));
        employeesCreated.add(new QueryOptionEmployee(new BigDecimal(199), "Bowser"));
        uow.registerAllObjects(employeesCreated);
        uow.commit();
    }

    @Override
    public void test() {
        DatabaseSession session = getDatabaseSession();
        testQueryTimeoutReset(session);
        testMaxRowsReset(session);
        testResultSetFetchSizeReset(session);
    }

    public void testMaxRowsReset(Session session) {
        // MAX ROWS
        // 1. Execute query to read employees with a max-rows set to 4
        ReadAllQuery query = new ReadAllQuery(QueryOptionEmployee.class);
        query.setMaxRows(4);
        List employees = (List) session.executeQuery(query);

        // 2. Check employees read = 4 per MaxRows setting - just with an assert
        if (employees.size() != 4) {
            throw new TestErrorException("Max Rows reset - Rows returned: " + employees.size() + " (expecting 4)");
        }

        // 3. Execute another (new) query 100 times with same SQL & no max-rows setting
        for (int iteration = 0; iteration < 100; iteration++) {
            query = new ReadAllQuery(QueryOptionEmployee.class);
            employees = (List) session.executeQuery(query);
            if (employees.size() <= 4) {
                throw new TestErrorException("Max Rows reset - Rows returned: " + employees.size() + " (expecting >= 10)");
            }
        }
    }

    public void testResultSetFetchSizeReset(Session session) {
        // H2 sets the query fetch size on the connection, and does not clear it, so this will fail.
        if (getSession().getLogin().getDatasourcePlatform().isH2()) {
            return;
        }
        // Resultset fetch size
        ReadAllQuery query = new ReadAllQuery(QueryOptionEmployee.class);
        // HANA supports only TYPE_FORWARD_ONLY and CONCUR_READ_ONLY
        if (getSession().getPlatform().isHANA()) {
            ScrollableCursorPolicy policy = new ScrollableCursorPolicy();
            policy.setResultSetType(ScrollableCursorPolicy.TYPE_FORWARD_ONLY);
            policy.setResultSetConcurrency(ScrollableCursorPolicy.CONCUR_READ_ONLY);
            policy.setPageSize(10);
            query.useScrollableCursor(policy);
        } else if(TYPE_SCROLL_INSENSITIVE_isSupported && CONCUR_UPDATABLE_isSupported) {
            query.useScrollableCursor(2);
        } else {
            ScrollableCursorPolicy policy = new ScrollableCursorPolicy();
            if(!TYPE_SCROLL_INSENSITIVE_isSupported) {
                policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_SENSITIVE);
            }
            if(!CONCUR_UPDATABLE_isSupported) {
                policy.setResultSetConcurrency(ScrollableCursorPolicy.CONCUR_READ_ONLY);
            }
            policy.setPageSize(10);
            query.useScrollableCursor(policy);
        }

        String sql = "SELECT ID, NAME, HISTORY_ID FROM QUERY_OPTION_EMPLOYEE";
        int fetchSize = 100;
        query.setSQLString(sql);
        query.setFetchSize(fetchSize);

        // The statement cache is protected - need to obtain the internal hashtable from the accessor
        org.eclipse.persistence.internal.sessions.DatabaseSessionImpl impl =
            (org.eclipse.persistence.internal.sessions.DatabaseSessionImpl) session;
        DatabaseAccessor accessor = (DatabaseAccessor) impl.getAccessor();
        Map statementCache = null;
        try {
            Method method = PrivilegedAccessHelper.getDeclaredMethod(DatabaseAccessor.class,
                "getStatementCache", new Class<?>[]{});
            method.setAccessible(true);
            statementCache = (Map) method.invoke(accessor, new Object[] {});
        } catch (Exception nsme) {
            throwError("Could not invoke DatabaseAccessor>>getStatementCache()", nsme);
        }

        // now cache the statement's previous fetch size
        int previousFetchSize = 0;
        Statement statement = (Statement) statementCache.get(sql);
        if (statement != null) {
            try {
                previousFetchSize = statement.getFetchSize();
            } catch (SQLException sqle) {
                throwError("Error whilst invoking intial Statement>>getFetchSize()", sqle);
            }
        }

        // execute query
        ScrollableCursor cursor = (ScrollableCursor) session.executeQuery(query);
        List employees = new ArrayList();
        while (cursor.hasNext()) {
            employees.add(cursor.next());
        }
        cursor.close();

        // now check the statement
        int postQueryFetchSize = 0;
        statement = (Statement) statementCache.get(sql);
        if (statement != null) {
            try {
                postQueryFetchSize = statement.getFetchSize();
            } catch (SQLException sqle) {
                throwError("Error whilst invoking secondary Statement>>getFetchSize()", sqle);
            }
        }

        if (postQueryFetchSize == fetchSize) {
            throwError("Statement fetch size was not reset");
        }

    }

    public void testQueryTimeoutReset(Session session) {
        if (getSession().getPlatform().isSymfoware()) {
            throwWarning("Test testQueryTimeoutReset skipped for this platform, "
                    + "the driver does not support query timeout. (bug 304905)");
        }
        boolean query1TimedOut = false;
        boolean query2TimedOut = false;
        // H2 sets the query timeout on the connection, and does not clear it, so this will fail.
        if (getSession().getLogin().getDatasourcePlatform().isH2()) {
            return;
        }
        String sql;
        if (getSession().getLogin().getDatasourcePlatform().isDB2() || getSession().getLogin().getDatasourcePlatform().isMySQL()) {
          sql = "SELECT SUM(e.EMP_ID) from EMPLOYEE e , EMPLOYEE b, EMPLOYEE c,EMPLOYEE d";
        } else {
          sql = "SELECT SUM(e.EMP_ID) from EMPLOYEE a , EMPLOYEE b, EMPLOYEE c, EMPLOYEE d, EMPLOYEE e, EMPLOYEE f, EMPLOYEE g";
        }
        // set the lowest timeout value on a query which is virtually guaranteed to produce a timeout
        try {
            DataReadQuery query = new DataReadQuery();
            query.setSQLString(sql);
            query.setQueryTimeout(1);
            session.executeQuery(query);
        } catch (Exception e) {
            if (e instanceof DatabaseException) {
                // cache value for debug purposes
                query1TimedOut = true;
            }
        }

        // do not set a timeout on the query, and test for a timeout
        try {
            DataReadQuery query = new DataReadQuery();
            query.setSQLString(sql);
            session.executeQuery(query);
        } catch (Exception e) {
            if (e instanceof DatabaseException) {
                query2TimedOut = true;
            }
        }

        // we're interested in if query 2 timed out
        // if no timeout value was set, query 2 should not produce a timeout
        if (query2TimedOut == true) {
            throw new TestErrorException("Query timeout occurred - PreparedStatement query timeout setting not cleared");
        }
    }

    @Override
    public void reset() {
        getDatabaseSession().getLogin().dontCacheAllStatements();
        UnitOfWork uow = getDatabaseSession().acquireUnitOfWork();
        uow.deleteAllObjects(employeesCreated);
        uow.commit();
        getDatabaseSession().getIdentityMapAccessor().initializeAllIdentityMaps();
    }

}
