/*
 * 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Time;
import java.sql.Timestamp;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(ParallelStatsDisabledTest.class)
public class ArrayFillFunctionIT extends ParallelStatsDisabledIT {

  private String tableName;

  @Before
  public void initTable() throws Exception {
    tableName = generateUniqueName();
    Connection conn = DriverManager.getConnection(getUrl());
    String ddl = "CREATE TABLE " + tableName
      + " (region_name VARCHAR PRIMARY KEY,length1 INTEGER, length2 INTEGER,\"DATE\" DATE,\"time\" TIME,\"timestamp\" TIMESTAMP,\"varchar\" VARCHAR,\"integer\" INTEGER,\"double\" DOUBLE,\"bigint\" BIGINT,\"char\" CHAR(15),double1 DOUBLE,char1 CHAR(17),nullcheck INTEGER,chars2 CHAR(15)[], varchars2 VARCHAR[])";
    conn.createStatement().execute(ddl);
    String dml = "UPSERT INTO " + tableName
      + "(region_name,length1,length2,\"DATE\",\"time\",\"timestamp\",\"varchar\",\"integer\",\"double\",\"bigint\",\"char\",double1,char1,nullcheck,chars2,varchars2) VALUES('SF Bay Area',"
      + "0," + "-3," + "to_date('2015-05-20 06:12:14.184')," + "to_time('2015-05-20 06:12:14.184'),"
      + "to_timestamp('2015-05-20 06:12:14.184')," + "'foo'," + "34," + "23.45," + "34567,"
      + "'foo'," + "23.45," + "'wert'," + "NULL," + "ARRAY['hello','hello','hello'],"
      + "ARRAY['hello','hello','hello']" + ")";
    PreparedStatement stmt = conn.prepareStatement(dml);
    stmt.execute();
    conn.commit();
  }

  @Test
  public void testArrayFillFunctionVarchar() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"varchar\",5) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    String[] strings = new String[] { "foo", "foo", "foo", "foo", "foo" };

    Array array = conn.createArrayOf("VARCHAR", strings);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInteger() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"integer\",4) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { 34, 34, 34, 34 };

    Array array = conn.createArrayOf("INTEGER", objects);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionDouble() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"double\",4) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { 23.45, 23.45, 23.45, 23.45 };

    Array array = conn.createArrayOf("DOUBLE", objects);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionBigint() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"bigint\",4) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { 34567l, 34567l, 34567l, 34567l };

    Array array = conn.createArrayOf("BIGINT", objects);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionChar() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"char\",4) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { "foo", "foo", "foo", "foo" };

    Array array = conn.createArrayOf("CHAR", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionVarChar() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"varchar\",4) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { "foo", "foo", "foo", "foo" };

    Array array = conn.createArrayOf("VARCHAR", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionDate() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"DATE\",3) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects =
      new Object[] { new Date(1432102334184l), new Date(1432102334184l), new Date(1432102334184l) };

    Array array = conn.createArrayOf("DATE", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionTime() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT ARRAY_FILL(\"time\",3) FROM " + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects =
      new Object[] { new Time(1432102334184l), new Time(1432102334184l), new Time(1432102334184l) };

    Array array = conn.createArrayOf("TIME", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionTimestamp() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY_FILL(\"timestamp\",3) FROM " + tableName
      + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { new Timestamp(1432102334184l), new Timestamp(1432102334184l),
      new Timestamp(1432102334184l) };

    Array array = conn.createArrayOf("TIMESTAMP", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test(expected = IllegalArgumentException.class)
  public void testArrayFillFunctionInvalidLength1() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY_FILL(\"timestamp\",length2) FROM "
      + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { new Timestamp(1432102334184l), new Timestamp(1432102334184l),
      new Timestamp(1432102334184l) };

    Array array = conn.createArrayOf("TIMESTAMP", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test(expected = IllegalArgumentException.class)
  public void testArrayFillFunctionInvalidLength2() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY_FILL(\"timestamp\",length1) FROM "
      + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { new Timestamp(1432102334184l), new Timestamp(1432102334184l),
      new Timestamp(1432102334184l) };

    Array array = conn.createArrayOf("TIMESTAMP", objects);
    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithNestedFunctions1() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY_FILL(ARRAY_ELEM(ARRAY[23,45],1),3) FROM "
      + tableName + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Integer[] integers = new Integer[] { 23, 23, 23 };

    Array array = conn.createArrayOf("INTEGER", integers);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithNestedFunctions2() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement()
      .executeQuery("SELECT ARRAY_FILL('hello', ARRAY_LENGTH(ARRAY[34, 45])) FROM " + tableName
        + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { "hello", "hello" };

    Array array = conn.createArrayOf("VARCHAR", objects);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithNestedFunctions3() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement()
      .executeQuery("SELECT ARRAY_FILL(3.4, ARRAY_LENGTH(ARRAY[34, 45])) FROM " + tableName
        + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Object[] objects = new Object[] { BigDecimal.valueOf(3.4), BigDecimal.valueOf(3.4) };

    Array array = conn.createArrayOf("DECIMAL", objects);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithUpsert1() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    String regions = generateUniqueName();
    String ddl =
      "CREATE TABLE " + regions + " (region_name VARCHAR PRIMARY KEY,varchars VARCHAR[])";
    conn.createStatement().execute(ddl);

    String dml = "UPSERT INTO " + regions
      + "(region_name,varchars) VALUES('SF Bay Area',ARRAY_FILL('hello',3))";
    conn.createStatement().execute(dml);
    conn.commit();

    ResultSet rs;
    rs = conn.createStatement()
      .executeQuery("SELECT varchars FROM " + regions + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    String[] strings = new String[] { "hello", "hello", "hello" };

    Array array = conn.createArrayOf("VARCHAR", strings);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithUpsert2() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    String regions = generateUniqueName();
    String ddl =
      "CREATE TABLE " + regions + " (region_name VARCHAR PRIMARY KEY,integers INTEGER[])";
    conn.createStatement().execute(ddl);

    String dml =
      "UPSERT INTO " + regions + "(region_name,integers) VALUES('SF Bay Area',ARRAY_FILL(3456,3))";
    conn.createStatement().execute(dml);
    conn.commit();

    ResultSet rs;
    rs = conn.createStatement()
      .executeQuery("SELECT integers FROM " + regions + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Integer[] integers = new Integer[] { 3456, 3456, 3456 };

    Array array = conn.createArrayOf("INTEGER", integers);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithUpsert3() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    String regions = generateUniqueName();
    String ddl = "CREATE TABLE " + regions + " (region_name VARCHAR PRIMARY KEY,doubles DOUBLE[])";
    conn.createStatement().execute(ddl);

    String dml =
      "UPSERT INTO " + regions + "(region_name,doubles) VALUES('SF Bay Area',ARRAY_FILL(2.5,3))";
    conn.createStatement().execute(dml);
    conn.commit();

    ResultSet rs;
    rs = conn.createStatement()
      .executeQuery("SELECT doubles FROM " + regions + " WHERE region_name = 'SF Bay Area'");
    assertTrue(rs.next());

    Double[] doubles = new Double[] { 2.5, 2.5, 2.5 };

    Array array = conn.createArrayOf("DOUBLE", doubles);

    assertEquals(array, rs.getArray(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithUpsertSelect1() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    String ddl = "CREATE TABLE source (region_name VARCHAR PRIMARY KEY,doubles DOUBLE[])";
    conn.createStatement().execute(ddl);

    ddl =
      "CREATE TABLE target (region_name VARCHAR PRIMARY KEY,doubles DOUBLE[],doubles2 DOUBLE[])";
    conn.createStatement().execute(ddl);

    String dml = "UPSERT INTO source(region_name,doubles) VALUES('SF Bay Area',ARRAY_FILL(3.4,3))";
    conn.createStatement().execute(dml);

    dml = "UPSERT INTO source(region_name,doubles) VALUES('SF Bay Area2',ARRAY_FILL(2.3,3))";
    conn.createStatement().execute(dml);
    conn.commit();

    dml =
      "UPSERT INTO target(region_name, doubles, doubles2) SELECT region_name, doubles,ARRAY_FILL(4.5,5) FROM source";
    conn.createStatement().execute(dml);
    conn.commit();

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT doubles, doubles2 FROM target");
    assertTrue(rs.next());

    Double[] doubles = new Double[] { 3.4, 3.4, 3.4 };
    Double[] doubles2 = new Double[] { 4.5, 4.5, 4.5, 4.5, 4.5 };
    Array array = conn.createArrayOf("DOUBLE", doubles);
    Array array2 = conn.createArrayOf("DOUBLE", doubles2);

    assertEquals(array, rs.getArray(1));
    assertEquals(array2, rs.getArray(2));
    assertTrue(rs.next());

    doubles = new Double[] { 2.3, 2.3, 2.3 };
    array = conn.createArrayOf("DOUBLE", doubles);

    assertEquals(array, rs.getArray(1));
    assertEquals(array2, rs.getArray(2));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionWithUpsertSelect2() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    String source = generateUniqueName();
    String ddl = "CREATE TABLE " + source + " (region_name VARCHAR PRIMARY KEY,varchars VARCHAR[])";
    conn.createStatement().execute(ddl);

    String target = generateUniqueName();
    ddl = "CREATE TABLE " + target
      + " (region_name VARCHAR PRIMARY KEY,varchars VARCHAR[],varchars2 VARCHAR[])";
    conn.createStatement().execute(ddl);

    String dml =
      "UPSERT INTO " + source + "(region_name,varchars) VALUES('SF Bay Area',ARRAY_FILL('foo',3))";
    conn.createStatement().execute(dml);

    dml = "UPSERT INTO " + source
      + "(region_name,varchars) VALUES('SF Bay Area2',ARRAY_FILL('hello',3))";
    conn.createStatement().execute(dml);
    conn.commit();

    dml = "UPSERT INTO " + target
      + "(region_name, varchars, varchars2) SELECT region_name, varchars,ARRAY_FILL(':-)',5) FROM "
      + source;
    conn.createStatement().execute(dml);
    conn.commit();

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT varchars, varchars2 FROM " + target);
    assertTrue(rs.next());

    String[] strings = new String[] { "foo", "foo", "foo" };
    String[] strings2 = new String[] { ":-)", ":-)", ":-)", ":-)", ":-)" };
    Array array = conn.createArrayOf("VARCHAR", strings);
    Array array2 = conn.createArrayOf("VARCHAR", strings2);

    assertEquals(array, rs.getArray(1));
    assertEquals(array2, rs.getArray(2));
    assertTrue(rs.next());

    strings = new String[] { "hello", "hello", "hello" };
    array = conn.createArrayOf("VARCHAR", strings);

    assertEquals(array, rs.getArray(1));
    assertEquals(array2, rs.getArray(2));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere1() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT region_name FROM " + tableName + " WHERE ARRAY[12, 12, 12, 12]=ARRAY_FILL(12,4)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere2() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT region_name FROM " + tableName + " WHERE \"varchar\"=ANY(ARRAY_FILL('foo',3))");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere3() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT region_name FROM " + tableName
      + " WHERE ARRAY['2345', '2345', '2345', '2345']=ARRAY_FILL('2345', 4)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere4() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT region_name FROM " + tableName
      + " WHERE ARRAY[23.45, 23.45, 23.45]=ARRAY_FILL(23.45, 3)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere5() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT region_name FROM " + tableName
      + " WHERE ARRAY['foo','foo','foo','foo','foo']=ARRAY_FILL(\"varchar\",5)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere6() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT region_name FROM " + tableName + " WHERE varchars2=ARRAY_FILL('hello',3)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }

  @Test
  public void testArrayFillFunctionInWhere7() throws Exception {
    Connection conn = DriverManager.getConnection(getUrl());

    ResultSet rs;
    rs = conn.createStatement().executeQuery(
      "SELECT region_name FROM " + tableName + " WHERE ARRAY[2,2,2,2]=ARRAY_FILL(2,4)");
    assertTrue(rs.next());

    assertEquals("SF Bay Area", rs.getString(1));
    assertFalse(rs.next());
  }
}
