/*
* Copyright (c) 2007, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.test.jdbc4;
import org.postgresql.core.ServerVersion;
import org.postgresql.geometric.PGbox;
import org.postgresql.jdbc.PreferQueryMode;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest4;
import org.postgresql.util.PGobject;
import org.postgresql.util.PGtokenizer;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
@RunWith(Parameterized.class)
public class ArrayTest extends BaseTest4 {
private Connection _conn;
public ArrayTest(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}
@Parameterized.Parameters(name = "binary = {0}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<Object[]>();
for (BinaryMode binaryMode : BinaryMode.values()) {
ids.add(new Object[]{binaryMode});
}
return ids;
}
@Override
public void setUp() throws Exception {
super.setUp();
_conn = con;
TestUtil.createTable(_conn, "arrtest",
"intarr int[], decarr decimal(2,1)[], strarr text[]"
+ (TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_3) ? ", uuidarr uuid[]" : "")
+ ", floatarr float8[]"
+ ", intarr2 int4[][]");
TestUtil.createTable(_conn, "arrcompprnttest", "id serial, name character(10)");
TestUtil.createTable(_conn, "arrcompchldttest",
"id serial, name character(10), description character varying, parent integer");
TestUtil.createTable(_conn, "\"CorrectCasing\"", "id serial");
TestUtil.createTable(_conn, "\"Evil.Table\"", "id serial");
}
@Override
public void tearDown() throws SQLException {
TestUtil.dropTable(_conn, "arrtest");
TestUtil.dropTable(_conn, "arrcompprnttest");
TestUtil.dropTable(_conn, "arrcompchldttest");
TestUtil.dropTable(_conn, "\"CorrectCasing\"");
super.tearDown();
}
@Test
public void testCreateArrayOfInt() throws SQLException {
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::int[]");
Integer in[] = new Integer[3];
in[0] = 0;
in[1] = -1;
in[2] = 2;
pstmt.setArray(1, _conn.createArrayOf("int4", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
Integer out[] = (Integer[]) arr.getArray();
Assert.assertEquals(3, out.length);
Assert.assertEquals(0, out[0].intValue());
Assert.assertEquals(-1, out[1].intValue());
Assert.assertEquals(2, out[2].intValue());
}
@Test
public void testCreateArrayOfMultiString() throws SQLException {
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::text[]");
String in[][] = new String[2][2];
in[0][0] = "a";
in[0][1] = "";
in[1][0] = "\\";
in[1][1] = "\"\\'z";
pstmt.setArray(1, _conn.createArrayOf("text", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
String out[][] = (String[][]) arr.getArray();
Assert.assertEquals(2, out.length);
Assert.assertEquals(2, out[0].length);
Assert.assertEquals("a", out[0][0]);
Assert.assertEquals("", out[0][1]);
Assert.assertEquals("\\", out[1][0]);
Assert.assertEquals("\"\\'z", out[1][1]);
}
@Test
public void testCreateArrayOfMultiJson() throws SQLException {
if (!TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v9_2)) {
return;
}
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::json[]");
PGobject p1 = new PGobject();
p1.setType("json");
p1.setValue("{\"x\": 10}");
PGobject p2 = new PGobject();
p2.setType("json");
p2.setValue("{\"x\": 20}");
PGobject in[] = new PGobject[] { p1, p2 };
pstmt.setArray(1, _conn.createArrayOf("json", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
ResultSet arrRs = arr.getResultSet();
Assert.assertTrue(arrRs.next());
Assert.assertEquals(in[0], arrRs.getObject(2));
Assert.assertTrue(arrRs.next());
Assert.assertEquals(in[1], arrRs.getObject(2));
}
@Test
public void testCreateArrayWithNonStandardDelimiter() throws SQLException {
PGbox in[] = new PGbox[2];
in[0] = new PGbox(1, 2, 3, 4);
in[1] = new PGbox(5, 6, 7, 8);
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::box[]");
pstmt.setArray(1, _conn.createArrayOf("box", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
ResultSet arrRs = arr.getResultSet();
Assert.assertTrue(arrRs.next());
Assert.assertEquals(in[0], arrRs.getObject(2));
Assert.assertTrue(arrRs.next());
Assert.assertEquals(in[1], arrRs.getObject(2));
Assert.assertFalse(arrRs.next());
}
@Test
public void testCreateArrayOfNull() throws SQLException {
String sql = "SELECT ?";
// We must provide the type information for V2 protocol
if (preferQueryMode == PreferQueryMode.SIMPLE) {
sql = "SELECT ?::int8[]";
}
PreparedStatement pstmt = _conn.prepareStatement(sql);
String in[] = new String[2];
in[0] = null;
in[1] = null;
pstmt.setArray(1, _conn.createArrayOf("int8", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
Long out[] = (Long[]) arr.getArray();
Assert.assertEquals(2, out.length);
Assert.assertNull(out[0]);
Assert.assertNull(out[1]);
}
@Test
public void testCreateEmptyArrayOfIntViaAlias() throws SQLException {
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::int[]");
Integer in[] = new Integer[0];
pstmt.setArray(1, _conn.createArrayOf("integer", in));
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
Integer out[] = (Integer[]) arr.getArray();
Assert.assertEquals(0, out.length);
ResultSet arrRs = arr.getResultSet();
Assert.assertFalse(arrRs.next());
}
@Test
public void testCreateArrayWithoutServer() throws SQLException {
String in[][] = new String[2][2];
in[0][0] = "a";
in[0][1] = "";
in[1][0] = "\\";
in[1][1] = "\"\\'z";
Array arr = _conn.createArrayOf("varchar", in);
String out[][] = (String[][]) arr.getArray();
Assert.assertEquals(2, out.length);
Assert.assertEquals(2, out[0].length);
Assert.assertEquals("a", out[0][0]);
Assert.assertEquals("", out[0][1]);
Assert.assertEquals("\\", out[1][0]);
Assert.assertEquals("\"\\'z", out[1][1]);
}
@Test
public void testCreatePrimitiveArray() throws SQLException {
double in[][] = new double[2][2];
in[0][0] = 3.5;
in[0][1] = -4.5;
in[1][0] = 10.0 / 3;
in[1][1] = 77;
Array arr = _conn.createArrayOf("float8", in);
Double out[][] = (Double[][]) arr.getArray();
Assert.assertEquals(2, out.length);
Assert.assertEquals(2, out[0].length);
Assert.assertEquals(3.5, out[0][0], 0.00001);
Assert.assertEquals(-4.5, out[0][1], 0.00001);
Assert.assertEquals(10.0 / 3, out[1][0], 0.00001);
Assert.assertEquals(77, out[1][1], 0.00001);
}
@Test
public void testUUIDArray() throws SQLException {
Assume.assumeTrue("UUID is not supported in PreferQueryMode.SIMPLE",
preferQueryMode != PreferQueryMode.SIMPLE);
Assume.assumeTrue("UUID requires PostgreSQL 8.3+",
TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_3));
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
UUID uuid3 = UUID.randomUUID();
// insert a uuid array, and check
PreparedStatement pstmt1 = _conn.prepareStatement("INSERT INTO arrtest(uuidarr) VALUES (?)");
pstmt1.setArray(1, _conn.createArrayOf("uuid", new UUID[]{uuid1, uuid2, uuid3}));
pstmt1.executeUpdate();
PreparedStatement pstmt2 =
_conn.prepareStatement("SELECT uuidarr FROM arrtest WHERE uuidarr @> ?");
pstmt2.setObject(1, _conn.createArrayOf("uuid", new UUID[]{uuid1}), Types.OTHER);
ResultSet rs = pstmt2.executeQuery();
Assert.assertTrue(rs.next());
Array arr = rs.getArray(1);
UUID out[] = (UUID[]) arr.getArray();
Assert.assertEquals(3, out.length);
Assert.assertEquals(uuid1, out[0]);
Assert.assertEquals(uuid2, out[1]);
Assert.assertEquals(uuid3, out[2]);
// concatenate a uuid, and check
UUID uuid4 = UUID.randomUUID();
PreparedStatement pstmt3 =
_conn.prepareStatement("UPDATE arrtest SET uuidarr = uuidarr || ? WHERE uuidarr @> ?");
pstmt3.setObject(1, uuid4, Types.OTHER);
pstmt3.setArray(2, _conn.createArrayOf("uuid", new UUID[]{uuid1}));
pstmt3.executeUpdate();
// --
pstmt2.setObject(1, _conn.createArrayOf("uuid", new UUID[]{uuid4}), Types.OTHER);
rs = pstmt2.executeQuery();
Assert.assertTrue(rs.next());
arr = rs.getArray(1);
out = (UUID[]) arr.getArray();
Assert.assertEquals(4, out.length);
Assert.assertEquals(uuid1, out[0]);
Assert.assertEquals(uuid2, out[1]);
Assert.assertEquals(uuid3, out[2]);
Assert.assertEquals(uuid4, out[3]);
}
@Test
public void testSetObjectFromJavaArray() throws SQLException {
String[] strArray = new String[]{"a", "b", "c"};
PreparedStatement pstmt = _conn.prepareStatement("INSERT INTO arrtest(strarr) VALUES (?)");
// Incorrect, but commonly attempted by many ORMs:
try {
pstmt.setObject(1, strArray, Types.ARRAY);
pstmt.executeUpdate();
Assert.fail("setObject() with a Java array parameter and Types.ARRAY shouldn't succeed");
} catch (org.postgresql.util.PSQLException ex) {
// Expected failure.
}
// Also incorrect, but commonly attempted by many ORMs:
try {
pstmt.setObject(1, strArray);
pstmt.executeUpdate();
Assert.fail("setObject() with a Java array parameter and no Types argument shouldn't succeed");
} catch (org.postgresql.util.PSQLException ex) {
// Expected failure.
}
// Correct way, though the use of "text" as a type is non-portable.
// Only supported for JDK 1.6 and JDBC4
Array sqlArray = _conn.createArrayOf("text", strArray);
pstmt.setArray(1, sqlArray);
pstmt.executeUpdate();
pstmt.close();
}
@Test
public void testGetArrayOfComposites() throws SQLException {
Assume.assumeTrue("array_agg(expression) requires PostgreSQL 8.4+",
TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_4));
PreparedStatement insert_parent_pstmt =
_conn.prepareStatement("INSERT INTO arrcompprnttest (name) "
+ "VALUES ('aParent');");
insert_parent_pstmt.execute();
String[] children = {
"November 5, 2013",
"\"A Book Title\"",
"4\" by 6\"",
"5\",3\""};
PreparedStatement insert_children_pstmt =
_conn.prepareStatement("INSERT INTO arrcompchldttest (name,description,parent) "
+ "VALUES ('child1',?,1),"
+ "('child2',?,1),"
+ "('child3',?,1),"
+ "('child4',?,1);");
insert_children_pstmt.setString(1, children[0]);
insert_children_pstmt.setString(2, children[1]);
insert_children_pstmt.setString(3, children[2]);
insert_children_pstmt.setString(4, children[3]);
insert_children_pstmt.execute();
PreparedStatement pstmt = _conn.prepareStatement(
"SELECT arrcompprnttest.name, "
+ "array_agg("
+ "DISTINCT(arrcompchldttest.id, "
+ "arrcompchldttest.name, "
+ "arrcompchldttest.description)) "
+ "AS children "
+ "FROM arrcompprnttest "
+ "LEFT JOIN arrcompchldttest "
+ "ON (arrcompchldttest.parent = arrcompprnttest.id) "
+ "WHERE arrcompprnttest.id=? "
+ "GROUP BY arrcompprnttest.name;");
pstmt.setInt(1, 1);
ResultSet rs = pstmt.executeQuery();
Assert.assertNotNull(rs);
Assert.assertTrue(rs.next());
Array childrenArray = rs.getArray("children");
Assert.assertNotNull(childrenArray);
ResultSet rsChildren = childrenArray.getResultSet();
Assert.assertNotNull(rsChildren);
while (rsChildren.next()) {
String comp = rsChildren.getString(2);
PGtokenizer token = new PGtokenizer(PGtokenizer.removePara(comp), ',');
token.remove("\"", "\""); // remove surrounding double quotes
if (2 < token.getSize()) {
int childID = Integer.parseInt(token.getToken(0));
// remove double quotes escaping with double quotes
String value = token.getToken(2).replace("\"\"", "\"");
Assert.assertEquals(children[childID - 1], value);
} else {
Assert.fail("Needs to have 3 tokens");
}
}
}
@Test
public void testCasingComposite() throws SQLException {
Assume.assumeTrue("Arrays of composite types requires PostgreSQL 8.3+",
TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_3));
PGobject cc = new PGobject();
cc.setType("\"CorrectCasing\"");
cc.setValue("(1)");
Object[] in = new Object[1];
in[0] = cc;
Array arr = _conn.createArrayOf("\"CorrectCasing\"", in);
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::\"CorrectCasing\"[]");
pstmt.setArray(1, arr);
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Object[] resArr = (Object[]) rs.getArray(1).getArray();
Assert.assertTrue(resArr[0] instanceof PGobject);
PGobject resObj = (PGobject) resArr[0];
Assert.assertEquals("(1)", resObj.getValue());
}
@Test
public void testCasingBuiltinAlias() throws SQLException {
Array arr = _conn.createArrayOf("INT", new Integer[]{1, 2, 3});
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::INT[]");
pstmt.setArray(1, arr);
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Integer[] resArr = (Integer[]) rs.getArray(1).getArray();
Assert.assertArrayEquals(new Integer[]{1, 2, 3}, resArr);
}
@Test
public void testCasingBuiltinNonAlias() throws SQLException {
Array arr = _conn.createArrayOf("INT4", new Integer[]{1, 2, 3});
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::INT4[]");
pstmt.setArray(1, arr);
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Integer[] resArr = (Integer[]) rs.getArray(1).getArray();
Assert.assertArrayEquals(new Integer[]{1, 2, 3}, resArr);
}
@Test
public void testEvilCasing() throws SQLException {
Assume.assumeTrue("Arrays of composite types requires PostgreSQL 8.3+",
TestUtil.haveMinimumServerVersion(_conn, ServerVersion.v8_3));
PGobject cc = new PGobject();
cc.setType("\"Evil.Table\"");
cc.setValue("(1)");
Object[] in = new Object[1];
in[0] = cc;
Array arr = _conn.createArrayOf("\"Evil.Table\"", in);
PreparedStatement pstmt = _conn.prepareStatement("SELECT ?::\"Evil.Table\"[]");
pstmt.setArray(1, arr);
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
Object[] resArr = (Object[]) rs.getArray(1).getArray();
Assert.assertTrue(resArr[0] instanceof PGobject);
PGobject resObj = (PGobject) resArr[0];
Assert.assertEquals("(1)", resObj.getValue());
}
@Test
public void testToString() throws SQLException {
Double[] d = new Double[4];
d[0] = 3.5;
d[1] = -4.5;
d[2] = null;
d[3] = 77.0;
Array arr = con.createArrayOf("float8", d);
PreparedStatement pstmt = con.prepareStatement("INSERT INTO arrtest(floatarr) VALUES (?)");
ResultSet rs = null;
try {
pstmt.setArray(1, arr);
pstmt.execute();
} finally {
TestUtil.closeQuietly(pstmt);
}
Statement stmt = null;
try {
stmt = con.createStatement();
rs = stmt.executeQuery("select floatarr from arrtest");
while (rs.next()) {
Array doubles = rs.getArray(1);
String actual = doubles.toString();
if (actual != null) {
// Remove all double quotes. They do not make a difference here.
actual = actual.replaceAll("\"", "");
// Replace X.0 with just X
actual = actual.replaceAll("\\.0+([^0-9])", "$1");
}
Assert.assertEquals("Array.toString should use square braces",
"{3.5,-4.5,NULL,77}", actual);
}
} finally {
TestUtil.closeQuietly(rs);
TestUtil.closeQuietly(stmt);
}
}
@Test
public void nullArray() throws SQLException {
PreparedStatement ps = con.prepareStatement("INSERT INTO arrtest(floatarr) VALUES (?)");
ps.setNull(1, Types.ARRAY, "float8");
ps.execute();
ps.close();
ps = con.prepareStatement("select floatarr from arrtest");
ResultSet rs = ps.executeQuery();
Assert.assertTrue("arrtest should contain a row", rs.next());
Array getArray = rs.getArray(1);
Assert.assertNull("null array should return null value on getArray", getArray);
Object getObject = rs.getObject(1);
Assert.assertNull("null array should return null on getObject", getObject);
}
@Test(expected = NullPointerException.class)
public void createNullArray() throws SQLException {
Array arr = con.createArrayOf("float8", null);
Assert.fail("createArrayOf(float8, null) should fail with NPE");
}
@Test
public void multiDimIntArray() throws SQLException {
Array arr = con.createArrayOf("int4", new int[][]{{1,2}, {3,4}});
PreparedStatement ps = con.prepareStatement("select ?::int4[][]");
ps.setArray(1, arr);
ResultSet rs = ps.executeQuery();
rs.next();
Array resArray = rs.getArray(1);
String stringValue = resArray.toString();
// Both {{"1","2"},{"3","4"}} and {{1,2},{3,4}} are the same array representation
stringValue = stringValue.replaceAll("\"", "");
Assert.assertEquals("{{1,2},{3,4}}", stringValue);
TestUtil.closeQuietly(rs);
TestUtil.closeQuietly(ps);
}
}