/*
 * 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.phoenix.end2end;

import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
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 static org.junit.Assert.fail;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.schema.ColumnAlreadyExistsException;
import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
import org.apache.phoenix.util.PropertiesUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/**
 * Basic tests for Phoenix dynamic upserting
 * @since 1.3
 */

@Category(ParallelStatsDisabledTest.class)
public class DynamicUpsertIT extends ParallelStatsDisabledIT {
  private String tableName;

  @Before
  public void doBeforeTestSetup() throws Exception {
    tableName = BaseTest.generateUniqueName();
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    String ddl = "create table " + tableName + "   (entry varchar not null primary key,"
      + "    a.dummy varchar," + "    b.dummy varchar)";
    conn.createStatement().execute(ddl);
    conn.close();
  }

  /**
   * Test a simple upsert with a dynamic Column
   */
  @Test
  public void testUpsert() throws Exception {
    String upsertquery = "UPSERT INTO " + tableName
      + " (entry, a.DynCol VARCHAR,a.dummy) VALUES ('dynEntry','DynValue','DynColValue')";
    String selectquery =
      "SELECT DynCol FROM " + tableName + " (a.DynCol VARCHAR) where entry='dynEntry'";
    // String selectquery = "SELECT * FROM "+TABLE;

    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    conn.setAutoCommit(true);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      int rowsInserted = statement.executeUpdate();
      assertEquals(1, rowsInserted);
      assertEquals(1, statement.getUpdateCount());
      // When not using an UPSERT variant (e.g., ON DUPLICATE KEY) that is not capable of
      // returning a row, we don't expect to get a result set.
      assertNull(statement.getResultSet());

      // since the upsert does not alter the schema check with a dynamicolumn
      PreparedStatement selectStatement = conn.prepareStatement(selectquery);
      ResultSet rs = selectStatement.executeQuery();
      assertTrue(rs.next());
      assertEquals("DynValue", rs.getString(1));
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of multiple dynamic Columns
   */
  @Test
  public void testMultiUpsert() throws Exception {
    String upsertquery = "UPSERT INTO " + tableName
      + " (entry, a.DynColA VARCHAR,b.DynColB varchar) VALUES('dynEntry','DynColValuea','DynColValueb')";
    String selectquery = "SELECT DynColA,entry,DynColB FROM " + tableName
      + " (a.DynColA VARCHAR,b.DynColB VARCHAR) where entry='dynEntry'";

    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    conn.setAutoCommit(true);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      int rowsInserted = statement.executeUpdate();
      assertEquals(1, rowsInserted);

      // since the upsert does not alter the schema check with a dynamicolumn
      statement = conn.prepareStatement(selectquery);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      assertEquals("DynColValuea", rs.getString(1));
      assertEquals("dynEntry", rs.getString(2));
      assertEquals("DynColValueb", rs.getString(3));
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of a full row with dynamic Columns
   */
  @Test
  public void testFullUpsert() throws Exception {
    String upsertquery = "UPSERT INTO " + tableName
      + " (a.DynColA VARCHAR,b.DynColB varchar) VALUES('dynEntry','aValue','bValue','DynColValuea','DynColValueb')";
    String selectquery = "SELECT entry,DynColA,a.dummy,DynColB,b.dummy FROM " + tableName
      + " (a.DynColA VARCHAR,b.DynColB VARCHAR) where entry='dynEntry'";

    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    conn.setAutoCommit(true);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      int rowsInserted = statement.executeUpdate();
      assertEquals(1, rowsInserted);

      // since the upsert does not alter the schema check with a dynamicolumn
      statement = conn.prepareStatement(selectquery);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      assertEquals("dynEntry", rs.getString(1));
      assertEquals("DynColValuea", rs.getString(2));
      assertEquals("aValue", rs.getString(3));
      assertEquals("DynColValueb", rs.getString(4));
      assertEquals("bValue", rs.getString(5));
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of a full row with dynamic Columns and unbalanced number of values
   */
  @Test
  public void testFullUnbalancedUpsert() throws Exception {
    String upsertquery = "UPSERT INTO " + tableName
      + " (a.DynCol VARCHAR,b.DynCol varchar) VALUES('dynEntry','aValue','bValue','dyncola')";

    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      statement.execute();
      fail();
    } catch (SQLException e) {
      assertEquals(SQLExceptionCode.UPSERT_COLUMN_NUMBERS_MISMATCH.getErrorCode(),
        e.getErrorCode());
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of prexisting schema defined columns and dynamic ones with different datatypes
   */
  @Test(expected = ColumnAlreadyExistsException.class)
  public void testAmbiguousStaticUpsert() throws Exception {
    String upsertquery =
      "UPSERT INTO " + tableName + " (a.dummy INTEGER,b.dummy INTEGER) VALUES(1,2)";
    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      statement.execute();
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of two conflicting dynamic columns
   */
  @Test(expected = ColumnAlreadyExistsException.class)
  public void testAmbiguousDynamicUpsert() throws Exception {
    String upsertquery =
      "UPSERT INTO " + tableName + " (a.DynCol VARCHAR,a.DynCol INTEGER) VALUES('dynCol',1)";
    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      statement.execute();
    } finally {
      conn.close();
    }
  }

  /**
   * Test an upsert of an undefined ColumnFamily dynamic columns
   */
  @Test(expected = ColumnFamilyNotFoundException.class)
  public void testFakeCFDynamicUpsert() throws Exception {
    String upsertquery = "UPSERT INTO " + tableName + " (fakecf.DynCol VARCHAR) VALUES('dynCol')";
    String url = getUrl() + ";";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(url, props);
    try {
      PreparedStatement statement = conn.prepareStatement(upsertquery);
      statement.execute();
    } finally {
      conn.close();
    }
  }
}
