/*
 * 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.assertArrayEquals;
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.Array;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import org.apache.phoenix.schema.types.PhoenixArray;
import org.apache.phoenix.util.PropertiesUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.util.StringUtil;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(ParallelStatsDisabledTest.class)
public class Array2IT extends ArrayIT {

  private static final String TEST_QUERY = "select ?[2] from \"SYSTEM\".\"CATALOG\" limit 1";

  @Test
  public void testFixedWidthCharArray() throws Exception {
    Connection conn;
    PreparedStatement stmt;
    ResultSet rs;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    conn.createStatement()
      .execute("CREATE TABLE  " + table + "  ( k VARCHAR PRIMARY KEY, a CHAR(5) ARRAY)");
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.getMetaData().getColumns(null, null, table, "A");
    assertTrue(rs.next());
    assertEquals(5, rs.getInt("COLUMN_SIZE"));
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    stmt = conn.prepareStatement("UPSERT INTO  " + table + "  VALUES(?,?)");
    stmt.setString(1, "a");
    String[] s = new String[] { "1", "2" };
    Array array = conn.createArrayOf("CHAR", s);
    stmt.setArray(2, array);
    stmt.execute();
    conn.commit();
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.createStatement().executeQuery("SELECT k, a[2] FROM  " + table);
    assertTrue(rs.next());
    assertEquals("a", rs.getString(1));
    assertEquals("2", rs.getString(2));
    conn.close();
  }

  @Test
  public void testSelectArrayUsingUpsertLikeSyntax() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_double_array FROM  " + table
      + "  WHERE a_double_array = CAST(ARRAY [ 25.343, 36.763, 37.56,386.63] AS DOUBLE ARRAY)";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      Double[] doubleArr = new Double[4];
      doubleArr[0] = 25.343;
      doubleArr[1] = 36.763;
      doubleArr[2] = 37.56;
      doubleArr[3] = 386.63;
      Array array = conn.createArrayOf("DOUBLE", doubleArr);
      PhoenixArray resultArray = (PhoenixArray) rs.getArray(1);
      assertEquals(resultArray, array);
      assertEquals("[25.343, 36.763, 37.56, 386.63]", rs.getString(1));
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testArrayIndexUsedInWhereClause() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    int a_index = 0;
    String query =
      "SELECT a_double_array[2] FROM  " + table + "  where a_double_array[" + a_index + "2]<?";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      Double[] doubleArr = new Double[1];
      doubleArr[0] = 40.0;
      conn.createArrayOf("DOUBLE", doubleArr);
      statement.setDouble(1, 40.0d);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      // Need to support primitive
      doubleArr = new Double[1];
      doubleArr[0] = 36.763;
      Double result = rs.getDouble(1);
      assertEquals(doubleArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testArrayIndexUsedInGroupByClause() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_double_array[2] FROM " + table + "  GROUP BY a_double_array[2]";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      Double[] doubleArr = new Double[1];
      doubleArr[0] = 40.0;
      conn.createArrayOf("DOUBLE", doubleArr);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      doubleArr = new Double[1];
      doubleArr[0] = 36.763;
      Double result = rs.getDouble(1);
      assertEquals(doubleArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testVariableLengthArrayWithNullValue() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, true, getUrl());
    String query = "SELECT a_string_array[2] FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      String result = rs.getString(1);
      assertNull(result);
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectSpecificIndexOfAVariableArrayAlongWithAnotherColumn1() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_string_array[3],A_INTEGER FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      String result = rs.getString(1);
      assertEquals(strArr[0], result);
      int a_integer = rs.getInt(2);
      assertEquals(1, a_integer);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectSpecificIndexOfAVariableArrayAlongWithAnotherColumn2() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT A_INTEGER, a_string_array[3] FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      int a_integer = rs.getInt(1);
      assertEquals(1, a_integer);
      String result = rs.getString(2);
      assertEquals(strArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectMultipleArrayColumns() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT  a_string_array[3], a_double_array[2] FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      Double[] doubleArr = new Double[1];
      doubleArr[0] = 36.763d;
      Double a_double = rs.getDouble(2);
      assertEquals(doubleArr[0], a_double);
      String result = rs.getString(1);
      assertEquals(strArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectSameArrayColumnMultipleTimesWithDifferentIndices() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_string_array[1], a_string_array[2], "
      + "a_string_array[3], a_double_array[1], a_double_array[2], a_double_array[3] " + "FROM  "
      + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      assertEquals("ABC", rs.getString(1));
      assertEquals("CEDF", rs.getString(2));
      assertEquals("XYZWER", rs.getString(3));
      assertEquals(25.343, rs.getDouble(4), 0.0);
      assertEquals(36.763, rs.getDouble(5), 0.0);
      assertEquals(37.56, rs.getDouble(6), 0.0);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectSameArrayColumnMultipleTimesWithSameIndices() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_string_array[3], a_string_array[3] FROM " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      String result = rs.getString(1);
      assertEquals(strArr[0], result);
      result = rs.getString(2);
      assertEquals(strArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testSelectSpecificIndexOfAVariableArray() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_string_array[3] FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = "XYZWER";
      String result = rs.getString(1);
      assertEquals(strArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testWithOutOfRangeIndex() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT a_double_array[100] FROM  " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      PhoenixArray resultArray = (PhoenixArray) rs.getArray(1);
      assertNull(resultArray);
    } finally {
      conn.close();
    }
  }

  @Test
  public void testArrayLengthFunctionForVariableLength() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT ARRAY_LENGTH(a_string_array) FROM " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      int result = rs.getInt(1);
      assertEquals(result, 4);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testArrayLengthFunctionForFixedLength() throws Exception {
    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    initTablesWithArrays(table, tenantId, null, false, getUrl());
    String query = "SELECT ARRAY_LENGTH(a_double_array) FROM " + table;
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      int result = rs.getInt(1);
      assertEquals(result, 4);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testArraySizeRoundtrip() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      ResultSet rs = conn.getMetaData().getColumns(null, null, StringUtil.escapeLike(table),
        StringUtil.escapeLike(SchemaUtil.normalizeIdentifier("x_long_array")));
      assertTrue(rs.next());
      assertEquals(5, rs.getInt("ARRAY_SIZE"));
      assertFalse(rs.next());

      rs = conn.getMetaData().getColumns(null, null, StringUtil.escapeLike(table),
        StringUtil.escapeLike(SchemaUtil.normalizeIdentifier("a_string_array")));
      assertTrue(rs.next());
      assertEquals(3, rs.getInt("ARRAY_SIZE"));
      assertFalse(rs.next());

      rs = conn.getMetaData().getColumns(null, null, StringUtil.escapeLike(table),
        StringUtil.escapeLike(SchemaUtil.normalizeIdentifier("a_double_array")));
      assertTrue(rs.next());
      assertEquals(0, rs.getInt("ARRAY_SIZE"));
      assertTrue(rs.wasNull());
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testVarLengthArrComparisonInWhereClauseWithSameArrays() throws Exception {
    Connection conn;
    PreparedStatement stmt;
    ResultSet rs;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    conn.createStatement().execute("CREATE TABLE  " + table
      + "  ( k VARCHAR PRIMARY KEY, a_string_array VARCHAR(100) ARRAY[4], b_string_array VARCHAR(100) ARRAY[4])");
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    stmt = conn.prepareStatement("UPSERT INTO  " + table + "  VALUES(?,?,?)");
    stmt.setString(1, "a");
    String[] s = new String[] { "abc", "def", "ghi", "jkl" };
    Array array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(2, array);
    s = new String[] { "abc", "def", "ghi", "jkl" };
    array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(3, array);
    stmt.execute();
    conn.commit();
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.createStatement().executeQuery(
      "SELECT k, a_string_array[2] FROM  " + table + "  where a_string_array=b_string_array");
    assertTrue(rs.next());
    assertEquals("a", rs.getString(1));
    assertEquals("def", rs.getString(2));
    conn.close();
  }

  @Test
  public void testVarLengthArrComparisonInWhereClauseWithDiffSizeArrays() throws Exception {
    Connection conn;
    PreparedStatement stmt;
    ResultSet rs;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    conn.createStatement().execute("CREATE TABLE  " + table
      + "  ( k VARCHAR PRIMARY KEY, a_string_array VARCHAR(100) ARRAY[4], b_string_array VARCHAR(100) ARRAY[4])");
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    stmt = conn.prepareStatement("UPSERT INTO  " + table + "  VALUES(?,?,?)");
    stmt.setString(1, "a");
    String[] s = new String[] { "abc", "def", "ghi", "jkll" };
    Array array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(2, array);
    s = new String[] { "abc", "def", "ghi", "jklm" };
    array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(3, array);
    stmt.execute();
    conn.commit();
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.createStatement().executeQuery(
      "SELECT k, a_string_array[2] FROM  " + table + "  where a_string_array<b_string_array");
    assertTrue(rs.next());
    assertEquals("a", rs.getString(1));
    assertEquals("def", rs.getString(2));
    conn.close();
  }

  @Test
  public void testVarLengthArrComparisonWithNulls() throws Exception {
    Connection conn;
    PreparedStatement stmt;
    ResultSet rs;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    conn.createStatement().execute("CREATE TABLE  " + table
      + "  ( k VARCHAR PRIMARY KEY, a_string_array VARCHAR(100) ARRAY[4], b_string_array VARCHAR(100) ARRAY[4])");
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    stmt = conn.prepareStatement("UPSERT INTO  " + table + "  VALUES(?,?,?)");
    stmt.setString(1, "a");
    String[] s = new String[] { "abc", "def", "ghi", "jkll", null, null, "xxx" };
    Array array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(2, array);
    s = new String[] { "abc", "def", "ghi", "jkll", null, null, null, "xxx" };
    array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(3, array);
    stmt.execute();
    conn.commit();
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.createStatement().executeQuery(
      "SELECT k, a_string_array[2] FROM  " + table + "  where a_string_array>b_string_array");
    assertTrue(rs.next());
    assertEquals("a", rs.getString(1));
    assertEquals("def", rs.getString(2));
    conn.close();
  }

  @Test
  public void testUpsertValuesWithNull() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    String query = "upsert into  " + table + " (ORGANIZATION_ID,ENTITY_ID,a_double_array) values('"
      + tenantId + "','00A123122312312',null)";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    // at
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      int executeUpdate = statement.executeUpdate();
      assertEquals(1, executeUpdate);
      conn.commit();
      statement.close();
      conn.close();
      // create another connection
      props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
      conn = DriverManager.getConnection(getUrl(), props);
      query = "SELECT ARRAY_ELEM(a_double_array,2) FROM  " + table;
      statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      // Need to support primitive
      Double[] doubleArr = new Double[1];
      doubleArr[0] = 0.0d;
      conn.createArrayOf("DOUBLE", doubleArr);
      Double result = rs.getDouble(1);
      assertEquals(doubleArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testUpsertValuesWithNullUsingPreparedStmt() throws Exception {

    String tenantId = getOrganizationId();
    String table = createTableWithArray(getUrl(), getDefaultSplits(tenantId), null);
    String query =
      "upsert into  " + table + " (ORGANIZATION_ID,ENTITY_ID,a_string_array) values(?, ?, ?)";
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    // at
    Connection conn = DriverManager.getConnection(getUrl(), props);
    try {
      PreparedStatement statement = conn.prepareStatement(query);
      statement.setString(1, tenantId);
      statement.setString(2, "00A123122312312");
      statement.setNull(3, Types.ARRAY);
      int executeUpdate = statement.executeUpdate();
      assertEquals(1, executeUpdate);
      conn.commit();
      statement.close();
      conn.close();
      // create another connection
      props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
      conn = DriverManager.getConnection(getUrl(), props);
      query = "SELECT ARRAY_ELEM(a_string_array,1) FROM  " + table;
      statement = conn.prepareStatement(query);
      ResultSet rs = statement.executeQuery();
      assertTrue(rs.next());
      String[] strArr = new String[1];
      strArr[0] = null;
      conn.createArrayOf("VARCHAR", strArr);
      String result = rs.getString(1);
      assertEquals(strArr[0], result);
      assertFalse(rs.next());
    } finally {
      conn.close();
    }
  }

  @Test
  public void testPKWithArray() throws Exception {
    Connection conn;
    PreparedStatement stmt;
    ResultSet rs;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    conn.createStatement().execute("CREATE TABLE  " + table
      + "  ( k VARCHAR, a_string_array VARCHAR(100) ARRAY[4], b_string_array VARCHAR(100) ARRAY[4] \n"
      + " CONSTRAINT pk PRIMARY KEY (k, b_string_array)) \n");
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    stmt = conn.prepareStatement("UPSERT INTO  " + table + "  VALUES(?,?,?)");
    stmt.setString(1, "a");
    String[] s = new String[] { "abc", "def", "ghi", "jkll", null, null, "xxx" };
    Array array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(2, array);
    s = new String[] { "abc", "def", "ghi", "jkll", null, null, null, "xxx" };
    array = conn.createArrayOf("VARCHAR", s);
    stmt.setArray(3, array);
    stmt.execute();
    conn.commit();
    conn.close();

    conn = DriverManager.getConnection(getUrl(), props);
    rs = conn.createStatement().executeQuery(
      "SELECT k, a_string_array[2] FROM  " + table + "  where b_string_array[8]='xxx'");
    assertTrue(rs.next());
    assertEquals("a", rs.getString(1));
    assertEquals("def", rs.getString(2));
    conn.close();
  }

  @Test
  public void testPKWithArrayNotInEnd() throws Exception {
    Connection conn;

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    try {
      conn.createStatement().execute("CREATE TABLE  " + table
        + "  ( a_string_array VARCHAR(100) ARRAY[4], b_string_array VARCHAR(100) ARRAY[4], k VARCHAR  \n"
        + " CONSTRAINT pk PRIMARY KEY (b_string_array, k))");
      conn.close();
      fail();
    } catch (SQLException e) {
    } finally {
      if (conn != null) {
        conn.close();
      }
    }

  }

  @Test
  public void testArrayRefToLiteralCharArraySameLengths() throws Exception {
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
      PreparedStatement stmt = conn.prepareStatement(TEST_QUERY);
      // Test with each element of the char array having same lengths
      Array array = conn.createArrayOf("CHAR", new String[] { "a", "b", "c" });
      stmt.setArray(1, array);
      ResultSet rs = stmt.executeQuery();
      assertTrue(rs.next());
      assertEquals("b", rs.getString(1));
      assertFalse(rs.next());
    }
  }

  @Test
  public void testArrayRefToLiteralCharArrayDiffLengths() throws Exception {
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
      PreparedStatement stmt = conn.prepareStatement(TEST_QUERY);
      // Test with each element of the char array having different lengths
      Array array = conn.createArrayOf("CHAR", new String[] { "a", "bb", "ccc" });
      stmt.setArray(1, array);
      ResultSet rs = stmt.executeQuery();
      assertTrue(rs.next());
      assertEquals("bb", rs.getString(1));
      assertFalse(rs.next());
    }
  }

  @Test
  public void testArrayRefToLiteralBinaryArray() throws Exception {
    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
      PreparedStatement stmt = conn.prepareStatement(TEST_QUERY);
      // Test with each element of the binary array having different lengths
      byte[][] bytes = { { 0, 0, 1 }, { 0, 0, 2, 0 }, { 0, 0, 0, 3, 4 } };
      Array array = conn.createArrayOf("BINARY", bytes);
      stmt.setArray(1, array);
      ResultSet rs = stmt.executeQuery();
      assertTrue(rs.next());
      // Note that all elements are padded to be of the same length
      // as the longest element of the byte array
      assertArrayEquals(new byte[] { 0, 0, 2, 0, 0 }, rs.getBytes(1));
      assertFalse(rs.next());
    }
  }

  @Test
  public void testArrayConstructorWithMultipleRows1() throws Exception {

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    String ddl =
      "CREATE TABLE  " + table + "  (region_name VARCHAR PRIMARY KEY, a INTEGER, b INTEGER)";
    conn.createStatement().execute(ddl);
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    PreparedStatement stmt =
      conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('a', 6,3)");
    stmt.execute();
    stmt = conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('b', 2,4)");
    stmt.execute();
    stmt = conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('c', 6,3)");
    stmt.execute();
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT COUNT(DISTINCT ARRAY[a,b]) from  " + table);
    assertTrue(rs.next());
    assertEquals(2, rs.getInt(1));
  }

  @Test
  public void testArrayConstructorWithMultipleRows2() throws Exception {

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    String ddl =
      "CREATE TABLE  " + table + "  (region_name VARCHAR PRIMARY KEY, a INTEGER, b INTEGER)";
    conn.createStatement().execute(ddl);
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    PreparedStatement stmt =
      conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('a', 6,3)");
    stmt.execute();
    stmt = conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('b', 2,4)");
    stmt.execute();
    stmt = conn.prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('c', 6,3)");
    stmt.execute();
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY[a,b] from  " + table + " ");
    assertTrue(rs.next());
    Array arr = conn.createArrayOf("INTEGER", new Object[] { 6, 3 });
    assertEquals(arr, rs.getArray(1));
    rs.next();
    arr = conn.createArrayOf("INTEGER", new Object[] { 2, 4 });
    assertEquals(arr, rs.getArray(1));
    rs.next();
    arr = conn.createArrayOf("INTEGER", new Object[] { 6, 3 });
    assertEquals(arr, rs.getArray(1));
    rs.next();
  }

  @Test
  public void testArrayConstructorWithMultipleRows3() throws Exception {

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    String ddl =
      "CREATE TABLE  " + table + "  (region_name VARCHAR PRIMARY KEY, a VARCHAR, b VARCHAR)";
    conn.createStatement().execute(ddl);
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    PreparedStatement stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('a', 'foo', 'abc')");
    stmt.execute();
    stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('b', 'abc', 'dfg')");
    stmt.execute();
    stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('c', 'foo', 'abc')");
    stmt.execute();
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT ARRAY[a,b] from  " + table + " ");
    assertTrue(rs.next());
    Array arr = conn.createArrayOf("VARCHAR", new Object[] { "foo", "abc" });
    assertEquals(arr, rs.getArray(1));
    rs.next();
    arr = conn.createArrayOf("VARCHAR", new Object[] { "abc", "dfg" });
    assertEquals(arr, rs.getArray(1));
    rs.next();
    arr = conn.createArrayOf("VARCHAR", new Object[] { "foo", "abc" });
    assertEquals(arr, rs.getArray(1));
    rs.next();
  }

  @Test
  public void testArrayConstructorWithMultipleRows4() throws Exception {

    Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
    Connection conn = DriverManager.getConnection(getUrl(), props);
    String table = generateUniqueName();
    String ddl =
      "CREATE TABLE  " + table + "  (region_name VARCHAR PRIMARY KEY, a VARCHAR, b VARCHAR)";
    conn.createStatement().execute(ddl);
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    PreparedStatement stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('a', 'foo', 'abc')");
    stmt.execute();
    stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('b', 'abc', 'dfg')");
    stmt.execute();
    stmt = conn
      .prepareStatement("UPSERT INTO  " + table + " (region_name, a, b) VALUES('c', 'foo', 'abc')");
    stmt.execute();
    conn.commit();
    conn.close();
    conn = DriverManager.getConnection(getUrl(), props);
    ResultSet rs;
    rs = conn.createStatement().executeQuery("SELECT COUNT(DISTINCT ARRAY[a,b]) from  " + table);
    assertTrue(rs.next());
    assertEquals(2, rs.getInt(1));
  }

}
