/**
* Copyright (C) 2009-2014 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.pg;
import com.foundationdb.junit.SelectedParameterizedRunner;
import com.foundationdb.sql.jdbc.util.PSQLException;
import com.foundationdb.sql.jdbc.util.PSQLState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized.Parameters;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Test to check that we handle negative options on binary encoded values
* (e.g. sending a short, when a long is expected)
*
* Note: only some types are binary encoded when passing to the server,
* for example, at the time of this writing, setBoolean does a text transfer
* with "0" or "1"
*/
@RunWith(SelectedParameterizedRunner.class)
public class PostgresServerJDBCBadTypesIT extends PostgresServerITBase
{
private final Boolean binary;
@Before
public void createTable() throws Exception {
// Most of these aren't exercised because JDBC only sends integers and float/double as binary over pg.
SimpleColumn columns[] = {
new SimpleColumn("col_boolean", "AKSQL_ boolean"),
new SimpleColumn("col_tinyint", "MCOMPAT_ tinyint"),
new SimpleColumn("col_varbinary", "MCOMPAT_ varbinary", 256L, null),
new SimpleColumn("col_date", "MCOMPAT_ date"),
new SimpleColumn("col_decimal", "MCOMPAT_ decimal", 5L, 2L),
new SimpleColumn("col_double", "MCOMPAT_ double"),
new SimpleColumn("col_float", "MCOMPAT_ float"),
new SimpleColumn("col_int", "MCOMPAT_ int"),
new SimpleColumn("col_bigint", "MCOMPAT_ bigint"),
new SimpleColumn("col_smallint", "MCOMPAT_ smallint"),
new SimpleColumn("col_varchar", "MCOMPAT_ varchar", 16L, null),
new SimpleColumn("col_time", "MCOMPAT_ time"),
new SimpleColumn("col_datetime", "MCOMPAT_ datetime"),
new SimpleColumn("col_guid", "AKSQL_ GUID"),
};
createTableFromTypes(SCHEMA_NAME, "types", false, false, columns);
}
@Before
public void ensureCorrectConnectionType() throws Exception {
forgetConnection();
}
@Parameters(name="{0}")
public static Iterable<Object[]> types() throws Exception {
return Arrays.asList(new Object[]{"Binary", true}, new Object[]{"Text", false});
}
public PostgresServerJDBCBadTypesIT(String name, Boolean binary) {
this.binary = binary;
}
private PreparedStatement setStmt(String columnName) throws Exception {
return getConnection().prepareStatement(String.format("INSERT INTO types(id,%s) VALUES(1,?)", columnName));
}
private void checkValue(String columnName, Object value, int jdbcType) throws Exception {
PreparedStatement getStmt = getConnection().prepareStatement(
String.format("SELECT %s FROM types WHERE id = ?", columnName));
getStmt.setInt(1, 1);
ResultSet rs = getStmt.executeQuery();
assertTrue(rs.next());
compareObjects(asObject(value, jdbcType), rs.getObject(1));
rs.close();
}
private void checkNoneSet() throws Exception {
PreparedStatement getStmt = getConnection().prepareStatement(
String.format("SELECT id FROM types WHERE id = ?"));
getStmt.setInt(1, 1);
ResultSet rs = getStmt.executeQuery();
assertFalse(rs.next());
rs.close();
}
@Test
public void testSetLongWithShort() throws Exception {
PreparedStatement set = setStmt("col_bigint");
set.setShort(1, (short)34);
set.executeUpdate();
checkValue("col_bigint", 34L, Types.BIGINT);
}
@Test
public void testSetDateWithLong() throws Exception {
PreparedStatement set = setStmt("col_datetime");
set.setLong(1, (long)2008);
set.executeUpdate();
checkValue("col_datetime", null, Types.DATE);
}
@Test
public void testSetShortWithLong() throws Exception {
PreparedStatement set = setStmt("col_smallint");
set.setLong(1, 34L);
set.executeUpdate();
checkValue("col_smallint", (short)34, Types.SMALLINT);
}
@Test
public void testSetShortWithTooBigLong() throws Exception {
PreparedStatement set = setStmt("col_smallint");
set.setLong(1, 342147483641L);
set.executeUpdate();
// It looks like the code currently does a cast, so (short)longValue
// The key for right now is to not crash.
checkValue("col_smallint", Short.MAX_VALUE, Types.SMALLINT);
}
@Test
public void testSetShortWithDouble() throws Exception {
PreparedStatement set = setStmt("col_smallint");
set.setDouble(1, 34);
set.executeUpdate();
checkValue("col_smallint", (short)34, Types.SMALLINT);
}
@Test
public void testSetShortWithTooBigDouble() throws Exception {
PreparedStatement set = setStmt("col_smallint");
set.setDouble(1, 34.33E128);
set.executeUpdate();
// Double's aren't integers, so it parses the double, then calls longValue(), which clamps, then casts
// which results in -1. The key for right now is to not crash.
checkValue("col_smallint", Short.MAX_VALUE, Types.SMALLINT);
}
@Test
public void testSetDoubleWithShort() throws Exception {
PreparedStatement set = setStmt("col_double");
set.setShort(1, (short)52);
set.executeUpdate();
checkValue("col_double", 52.0, Types.DOUBLE);
}
@Test
public void testSetVarcharWithShort() throws Exception {
PreparedStatement set = setStmt("col_varchar");
set.setShort(1, (short)52);
set.executeUpdate();
checkValue("col_varchar", "52", Types.VARCHAR);
}
@Override
protected String getConnectionURL() {
// loglevel=2 is also useful for seeing what's really happening.
return super.getConnectionURL() + "?prepareThreshold=1&binaryTransfer=" + binary;
}
protected static Object asObject(Object value, int jdbcType) {
switch (jdbcType) {
case Types.TINYINT:
return ((Byte)value).intValue();
case Types.SMALLINT:
return ((Short)value).intValue();
default:
return value;
}
}
protected static void compareObjects(Object expected, Object actual) {
if (expected instanceof byte[]) {
assertTrue("Expected " + Arrays.toString((byte[])expected) + " but got " + Arrays.toString((byte[])actual),
Arrays.equals((byte[])expected, (byte[])actual));
}
else if (expected instanceof java.util.Date) {
assertEquals(String.format("%s <> %s",
((java.util.Date)expected).getTime(),
((java.util.Date)actual).getTime()),
expected, actual);
}
else {
assertEquals(expected, actual);
}
}
}