/*
 * 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.testing.tests.feature;

import java.io.*;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.testing.models.employee.domain.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.sequencing.*;
import org.eclipse.persistence.tools.schemaframework.*;
import org.eclipse.persistence.sessions.*;

/**
 * Test custom sequence-table access
 */
public class CustomSequenceTest extends AutoVerifyTestCase {

    /** the original setting */
    private Sequence originalSequence;

    /** the new sequence table name */
    private String newSeqTableName;

    /** the initial value of the counter for EMP_SEQ in the new table */
    private int initialSeqCount;

    /** the Employee being inserted using the new Seq Table */
    private Employee newEmployee;

    public CustomSequenceTest() {
        setDescription("This test verifies that sequencing can be customized using setUpdateSequenceQuery(DataModifyQuery) and setSelectSequenceNumberQuery(ValueReadQuery).");
    }

    protected void buildNEW_SEQUENCETable() {
        TableDefinition tabledefinition = new TableDefinition();

        // SECTION: TABLE
        tabledefinition.setName(this.newSeqTableName);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field = new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field.setName("SEQ_NAME");
        field.setType(String.class);
        field.setSize(50);
        field.setShouldAllowNull(true);
        field.setIsPrimaryKey(false);
        field.setUnique(false);
        field.setIsIdentity(false);
        tabledefinition.addField(field);

        // SECTION: FIELD
        org.eclipse.persistence.tools.schemaframework.FieldDefinition field1 = new org.eclipse.persistence.tools.schemaframework.FieldDefinition();
        field1.setName("SEQ_COUNT");
        field1.setType(Long.class);
        field1.setSize(15);
        field1.setShouldAllowNull(true);
        field1.setIsPrimaryKey(false);
        field1.setUnique(false);
        field1.setIsIdentity(false);
        tabledefinition.addField(field1);

        new SchemaManager((DatabaseSession)getSession()).replaceObject(tabledefinition);
    }

    public void reset() {
        rollbackTransaction();
        getSession().getIdentityMapAccessor().initializeIdentityMaps();

        // Change Sequence Query stuff back to normal
        getSession().getProject().getLogin().setDefaultSequence(this.originalSequence);
        // Don't want the sequence numbers obtained from another sequence table to be used with the regular one.
        // Note that before rollbackTransaction used to wipe out all sequencing numbers, but now
        // sequencing numbers remain no matter how transaction ends (unless there is no separate sequencing
        // connection used)
        ((DatabaseSession)getSession()).getSequencingControl().initializePreallocated();
    }

    public void setup() {
        org.eclipse.persistence.internal.databaseaccess.DatabasePlatform p = getSession().getProject().getLogin().getPlatform();

        /**
         * Build another sequence table for our custom sequencing.
         */
        this.newSeqTableName = new String("NEW_SEQUENCE");
        this.initialSeqCount = 1233;
        buildNEW_SEQUENCETable();
        getSession().executeNonSelectingCall(new org.eclipse.persistence.queries.SQLCall("INSERT INTO " + this.newSeqTableName + "(" + p.getSequenceNameFieldName() + ", " + p.getSequenceCounterFieldName() + ") values ('EMP_SEQ', " + this.initialSeqCount + ")"));

        /**
         * Set the Sequence Query Stuff to use our new sequence table.
         */
        // Save the old settings
        this.originalSequence = getSession().getLogin().getDefaultSequence();

        // Build the new custom queries
        DataModifyQuery newUpdateSeqQuery = new DataModifyQuery();
        newUpdateSeqQuery.addArgument("seqName");
        StringWriter writer1 = new StringWriter();
        writer1.write("UPDATE " + this.newSeqTableName);
        writer1.write(" SET " + p.getSequenceCounterFieldName());
        writer1.write(" = " + p.getSequenceCounterFieldName());
        writer1.write(" + " + p.getSequencePreallocationSize());
        writer1.write(" WHERE " + p.getSequenceNameFieldName());
        writer1.write(" = #seqName");
        newUpdateSeqQuery.setSQLString(writer1.toString());

        ValueReadQuery newSelectSeqNumQuery = new ValueReadQuery();
        newSelectSeqNumQuery.addArgument("seqName");
        StringWriter writer2 = new StringWriter();
        writer2.write("SELECT " + p.getSequenceCounterFieldName());
        writer2.write(" FROM " + this.newSeqTableName);
        writer2.write(" WHERE " + p.getSequenceNameFieldName());
        writer2.write(" = #seqName");
        newSelectSeqNumQuery.setSQLString(writer2.toString());

        // Set the new queries
        QuerySequence newSequence = new QuerySequence();
        newSequence.setUpdateQuery(newUpdateSeqQuery);
        newSequence.setSelectQuery(newSelectSeqNumQuery);
        getSession().getLogin().setDefaultSequence(newSequence);
        ((DatabaseSession)getSession()).getSequencingControl().resetSequencing();
        ((DatabaseSession)getSession()).getSequencingControl().initializePreallocated();
    }

    public void test() {
        beginTransaction();

        // write a new Employee to the DB
        this.newEmployee = new org.eclipse.persistence.testing.models.employee.domain.Employee();
        this.newEmployee.setFirstName("Brendan");
        this.newEmployee.setLastName("Rickey");
        UnitOfWork uow1 = getSession().acquireUnitOfWork();
        uow1.registerObject(this.newEmployee);
        uow1.commit();

        // update the Employee
        UnitOfWork uow2 = getSession().acquireUnitOfWork();
        Employee empRead = (Employee)uow2.readObject(this.newEmployee);
        empRead.setFirstName("Object");
        empRead.setLastName("People");
        uow2.commit();
    }

    public void verify() {
        //make sure the Employee was inserted and updated, and that the new Seq table was used.
        Employee verifyEmp = (Employee)getSession().readObject(this.newEmployee);
        if (verifyEmp.getId().intValue() != (this.initialSeqCount + 1)) {
            throw new TestErrorException("The new Sequence Table was not used! " + verifyEmp.getId());
        }
    }
}
