/*
 * 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.openjpa.jdbc.sql;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.sql.DataSource;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.kernel.StoreContext;
import org.hsqldb.types.Types;
import org.jmock.Expectations;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.junit.Rule;
import org.junit.Test;

public class TestHerdDBDictionary {
    @Rule
    public JUnitRuleMockery context = new JUnitRuleMockery();

    final Statement mockStatement = context.mock(Statement.class);
    final Connection mockConnection = context.mock(Connection.class);
    final ResultSet mockRS = context.mock(ResultSet.class);
    final DataSource mockDS = context.mock(DataSource.class);
    final DatabaseMetaData mockMetaData = context.mock(DatabaseMetaData.class);

    final StoreContext sc = null;

    @Test
    public void testBootDBDictionary() throws Exception {
        // Expected method calls on the mock objects above. If any of these are
        // do not occur, or if any other methods are invoked on the mock objects
        // an exception will be thrown and the test will fail.
        context.checking(new Expectations()
        {
            {

                allowing(mockConnection).getMetaData();
                will(returnValue(mockMetaData));

                allowing(mockMetaData).getDatabaseProductName();
                will(returnValue("MockDB"));

                allowing(mockMetaData).getDriverName();
                will(returnValue("MockDB"));

                allowing(mockMetaData).getDriverVersion();
                will(returnValue("1.0"));

                allowing(mockMetaData).getDatabaseProductVersion();
                will(returnValue("10"));

                allowing(mockMetaData).getDatabaseMajorVersion();
                will(returnValue(10));

                allowing(mockMetaData).getDatabaseMinorVersion();
                will(returnValue(0));

                allowing(mockMetaData).getJDBCMajorVersion();
                will(returnValue(4));

                allowing(mockMetaData).getJDBCMinorVersion();
                will(returnValue(0));

                allowing(mockMetaData).supportsMixedCaseIdentifiers();
                will(returnValue(true));

                allowing(mockMetaData).supportsMixedCaseQuotedIdentifiers();
                will(returnValue(true));

                allowing(mockMetaData).storesLowerCaseQuotedIdentifiers();
                will(returnValue(true));

                allowing(mockMetaData).storesUpperCaseQuotedIdentifiers();
                will(returnValue(false));

                allowing(mockMetaData).storesMixedCaseQuotedIdentifiers();
                will(returnValue(false));

                allowing(mockMetaData).supportsGetGeneratedKeys();
                will(returnValue(false));

            }
        });
        JDBCConfiguration jdbcConfiguration = new JDBCConfigurationImpl(false);

        DBDictionary dict = new HerdDBDictionary();
        dict.setConfiguration(jdbcConfiguration);
        assertNull(dict.getDefaultSchemaName());
        dict.connectedConfiguration(mockConnection);

        assertTrue(dict.supportsForeignKeys);
        assertTrue(dict.supportsUniqueConstraints);
        assertTrue(dict.supportsCascadeDeleteAction);
        assertFalse(dict.supportsCascadeUpdateAction);
        assertFalse(dict.supportsDeferredConstraints);

        SchemaGroup schemaGroup = new SchemaGroup();
        Schema schema = new Schema(DBIdentifier.newSchema("herddb", true), schemaGroup);

        Table parentTable = new Table(DBIdentifier.newTable("parentTable", true), schema);
        Column p1 = parentTable.addColumn(DBIdentifier.newColumn("p1", true));
        p1.setType(Types.VARCHAR);

        Table childTable = new Table(DBIdentifier.newTable("childTable", true), schema);
        childTable.setComment("This is a comment");
        Column k1 = childTable.addColumn(DBIdentifier.newColumn("k1", true));
        k1.setType(Types.VARCHAR);
        Column n1 = childTable.addColumn(DBIdentifier.newColumn("n1", true));
        n1.setType(Types.INTEGER);
        childTable.addPrimaryKey().addColumn(k1);

        childTable.addUnique(DBIdentifier.newConstraint("un1")).addColumn(n1);

        ForeignKey fk1 = childTable.addForeignKey(DBIdentifier.newForeignKey("fk1", true));
        fk1.setDeleteAction(ForeignKey.ACTION_CASCADE);
        fk1.join(n1, p1);

        String[] createTableSQL = dict.getCreateTableSQL(childTable);
        assertEquals("CREATE TABLE `herddb`.`childTable` (`k1` VARCHAR NOT NULL, `n1` INTEGER, "
                + "PRIMARY KEY (`k1`), CONSTRAINT `un1` UNIQUE (`n1`))", createTableSQL[0]);
        assertEquals(1, createTableSQL.length);

        String[] addForeignKeySQL = dict.getAddForeignKeySQL(fk1);
        assertEquals("ALTER TABLE `herddb`.`childTable` ADD CONSTRAINT `fk1` "
                + "FOREIGN KEY (`n1`) REFERENCES `herddb`.`parentTable` (`p1`) ON DELETE CASCADE", addForeignKeySQL[0]);
        assertEquals(1, addForeignKeySQL.length);

        String[] dropForeignKeySQL = dict.getDropForeignKeySQL(fk1, mockConnection);
        assertEquals("ALTER TABLE `herddb`.`childTable` DROP CONSTRAINT `fk1`", dropForeignKeySQL[0]);
        assertEquals(1, dropForeignKeySQL.length);


        ForeignKey fk2 = childTable.addForeignKey(DBIdentifier.newForeignKey("fk2", true));
        fk2.setDeleteAction(ForeignKey.ACTION_RESTRICT);
        fk2.setUpdateAction(ForeignKey.ACTION_CASCADE); // not supported
        fk2.join(n1, p1);

        // ON UPDATE CASCADE is not supported, so we are not adding the constraint
        String[] addForeignKeySQL2 = dict.getAddForeignKeySQL(fk2);
        assertEquals(0, addForeignKeySQL2.length);

        // ON UPDATE SET NULL is supported
        // ON DELETE RESTRICT is the default behaviour, so no need to write it in DDL
        fk2.setUpdateAction(ForeignKey.ACTION_NULL);
        String[] addForeignKeySQL3 = dict.getAddForeignKeySQL(fk2);
        assertEquals("ALTER TABLE `herddb`.`childTable` ADD CONSTRAINT `fk2` "
                + "FOREIGN KEY (`n1`) REFERENCES `herddb`.`parentTable` (`p1`) ON UPDATE SET NULL", addForeignKeySQL3[0]);
        assertEquals(1, addForeignKeySQL3.length);
    }

}
