blob: f723f2560aa221ca4b141dc8849c2fa847d5c1ee [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.integration;
import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.Calendar;
import java.util.TimeZone;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mariadb.jdbc.Connection;
import org.mariadb.jdbc.MariaDbBlob;
import org.mariadb.jdbc.MariaDbClob;
import org.mariadb.jdbc.Statement;
public class PreparedStatementParametersTest extends Common {
@BeforeAll
public static void beforeAll2() throws SQLException {
drop();
Statement stmt = sharedConn.createStatement();
stmt.execute("CREATE TABLE bigTest (t1 int not null primary key auto_increment, t2 LONGTEXT)");
stmt.execute("CREATE TABLE prepareParam (t1 BLOB(20))");
stmt.execute("CREATE TABLE prepareParam2 (t1 BIGINT)");
stmt.execute("CREATE TABLE prepareParam3 (t1 DOUBLE)");
stmt.execute(
"CREATE TABLE prepareParam4 (t1 VARCHAR(30)) "
+ "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
stmt.execute("CREATE TABLE prepareParam5 (t1 TIMESTAMP(6))");
stmt.execute("CREATE TABLE prepareParam6 (t1 BIGINT)");
stmt.execute("CREATE TABLE prepareParam7 (t1 TIME(6))");
stmt.execute("FLUSH TABLES");
}
@AfterAll
public static void drop() throws SQLException {
Statement stmt = sharedConn.createStatement();
stmt.execute("DROP TABLE IF EXISTS bigTest");
stmt.execute("DROP TABLE IF EXISTS prepareParam");
stmt.execute("DROP TABLE IF EXISTS prepareParam2");
stmt.execute("DROP TABLE IF EXISTS prepareParam3");
stmt.execute("DROP TABLE IF EXISTS prepareParam4");
stmt.execute("DROP TABLE IF EXISTS prepareParam5");
stmt.execute("DROP TABLE IF EXISTS prepareParam6");
stmt.execute("DROP TABLE IF EXISTS prepareParam7");
}
@Test
public void validateParameters() throws Exception {
// error crashing maxscale 6.1.x
Assumptions.assumeTrue(
!sharedConn.getMetaData().getDatabaseProductVersion().contains("maxScale-6.1.")
&& !"skysql-ha".equals(System.getenv("srv")));
validateParameters(sharedConn);
validateParameters(sharedConnBinary);
}
public void validateParameters(Connection con) throws Exception {
try (PreparedStatement prep = con.prepareStatement("INSERT INTO prepareParam6 VALUES (?)")) {
try {
prep.execute();
fail();
} catch (SQLException sqle) {
// eat
}
}
try (PreparedStatement prep = con.prepareStatement("INSERT INTO prepareParam6 VALUES (?)")) {
prep.setInt(1, 1);
prep.execute();
prep.clearParameters();
Common.assertThrowsContains(
SQLTransientConnectionException.class,
prep::execute,
"Parameter at position 1 is not set");
}
}
@Test
public void checkParameters() throws Exception {
checkParameters(sharedConn, true);
checkParameters(sharedConnBinary, false);
}
@SuppressWarnings("deprecation")
public void checkParameters(org.mariadb.jdbc.Connection con, boolean text) throws Exception {
Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
checkSendBlob(
ps -> ps.setBlob(1, new MariaDbBlob("0123".getBytes(), 1, 2)),
rs -> assertArrayEquals("12".getBytes(), rs.getBytes(1)),
con);
checkSendBlob(
ps -> ps.setBlob(1, new MariaDbBlob("0123".getBytes(), 1, 2).getBinaryStream()),
rs -> assertArrayEquals("12".getBytes(), rs.getBytes(1)),
con);
checkSendBlob(
ps -> ps.setBlob(1, new MariaDbBlob("01234".getBytes(), 1, 3).getBinaryStream(), 2),
rs -> assertArrayEquals("12".getBytes(), rs.getBytes(1)),
con);
checkSendBlob(
ps -> ps.setBlob(1, new MariaDbBlob("01234".getBytes(), 1, 3).getBinaryStream(), 2L),
rs -> assertArrayEquals("12".getBytes(), rs.getBytes(1)),
con);
checkSendBlob(ps -> ps.setNull(1, Types.VARBINARY), rs -> assertNull(rs.getObject(1)), con);
checkSendBlob(
ps -> ps.setNull(1, Types.VARBINARY, String.class.getName()),
rs -> assertNull(rs.getObject(1)),
con);
checkSendLong(ps -> ps.setBoolean(1, true), rs -> assertTrue(rs.getBoolean(1)), con);
checkSendLong(ps -> ps.setBoolean(1, false), rs -> assertFalse(rs.getBoolean(1)), con);
checkSendLong(
ps -> ps.setByte(1, (byte) 0x58), rs -> assertEquals((byte) 0x58, rs.getByte(1)), con);
checkSendLong(
ps -> ps.setShort(1, (short) 127), rs -> assertEquals((short) 127, rs.getShort(1)), con);
checkSendLong(ps -> ps.setInt(1, -555), rs -> assertEquals(-555, rs.getInt(1)), con);
checkSendLong(ps -> ps.setLong(1, -999L), rs -> assertEquals(-999L, rs.getLong(1)), con);
checkSendDouble(
ps -> ps.setFloat(1, -56.59F), rs -> assertEquals(-56.59F, rs.getFloat(1), 0.01F), con);
checkSendDouble(
ps -> ps.setDouble(1, -56.59D), rs -> assertEquals(-56.59D, rs.getDouble(1), 0.01D), con);
checkSendDouble(
ps -> ps.setBigDecimal(1, new BigDecimal("-156.59")),
rs ->
assertEquals(
new BigDecimal("-156.59"), rs.getBigDecimal(1).setScale(2, RoundingMode.HALF_DOWN)),
con);
checkSendString(
ps -> ps.setString(1, "你好(hello in Chinese)"),
rs -> assertEquals("你好(hello in Chinese)", rs.getString(1)),
con);
checkSendString(
ps -> ps.setNString(1, "你好(hello in Chinese)"),
rs -> assertEquals("你好(hello in Chinese)", rs.getNString(1)),
con);
checkSendBlob(
ps -> ps.setBytes(1, "01234".getBytes()),
rs -> assertArrayEquals("01234".getBytes(), rs.getBytes(1)),
con);
checkSendTimestamp(
ps -> ps.setDate(1, Date.valueOf("2010-05-25")),
rs -> assertEquals(Date.valueOf("2010-05-25"), rs.getDate(1)),
con);
boolean minus = TimeZone.getDefault().getOffset(System.currentTimeMillis()) > 0;
checkSendTimestamp(
ps -> ps.setDate(1, Date.valueOf("2010-01-12"), utcCal),
rs ->
assertEquals(minus ? 1263164400000L : 1263254400000L, rs.getDate(1, utcCal).getTime()),
con);
checkSendTimestamp(
ps -> ps.setDate(1, Date.valueOf("2010-01-12"), utcCal),
rs -> assertEquals(minus ? "2010-01-11" : "2010-01-12", rs.getDate(1, utcCal).toString()),
con);
checkSendTimestamp(
ps -> ps.setDate(1, Date.valueOf("2010-05-25")),
rs -> assertEquals(Date.valueOf("2010-05-25").getTime(), rs.getDate(1).getTime()),
con);
if (isXpand()) {
// skip due to error https://jira.mariadb.org/browse/XPT-265
} else {
if (text) {
Common.assertThrowsContains(
SQLException.class,
() ->
checkSendTimestamp(
ps -> ps.setTime(1, new Time(Time.valueOf("18:16:01").getTime() + 123)),
rs ->
assertEquals(
Time.valueOf("18:16:01").getTime() + 123, rs.getTime(1).getTime()),
con),
"Incorrect datetime value: '18:16:01.123'");
} else {
checkSendTimestamp(
ps -> ps.setTime(1, new Time(Time.valueOf("18:16:01").getTime() + 123)),
rs -> assertEquals(Time.valueOf("18:16:01").getTime() + 123, rs.getTime(1).getTime()),
con);
}
}
checkSendTimestamp(
ps -> ps.setTimestamp(1, Timestamp.valueOf("2010-05-25 18:16:01.987")),
rs ->
assertEquals(
Timestamp.valueOf("2010-05-25 18:16:01.987").getTime(),
rs.getTimestamp(1).getTime()),
con);
checkSendTimestamp(
ps -> ps.setTimestamp(1, Timestamp.valueOf("2010-05-25 18:16:01.987")),
rs -> assertEquals("2010-05-25 18:16:01.987", rs.getTimestamp(1).toString()),
con);
checkSendTimestamp(
ps -> ps.setTimestamp(1, Timestamp.valueOf("2010-05-25 18:16:01.987"), utcCal),
rs ->
assertEquals(
Timestamp.valueOf("2010-05-25 18:16:01.987").getTime(),
rs.getTimestamp(1, utcCal).getTime()),
con);
checkSendTimestamp(
ps -> ps.setTimestamp(1, Timestamp.valueOf("2010-05-25 18:16:01.987"), utcCal),
rs ->
assertEquals(
Timestamp.valueOf("2010-05-25 18:16:01.987").getTime()
- TimeZone.getDefault().getOffset(1, 2010, 5, 25, 1, 987),
rs.getTimestamp(1).getTime()),
con);
checkSendString(
ps -> ps.setAsciiStream(1, new ByteArrayInputStream("abcdef".getBytes())),
rs -> assertEquals("abcdef", rs.getString(1)),
con);
checkSendString(
ps -> ps.setAsciiStream(1, new ByteArrayInputStream("abcdef".getBytes()), 5),
rs -> assertEquals("abcde", rs.getString(1)),
con);
checkSendString(
ps -> ps.setAsciiStream(1, new ByteArrayInputStream("abcdef".getBytes()), 5L),
rs -> assertEquals("abcde", rs.getString(1)),
con);
final String unicodeString =
""
+ "\uD83D\uDE0E" // 😎 unicode 6 smiling face with sunglasses
+ "\uD83C\uDF36" // 🌶 unicode 7 hot pepper
+ "\uD83C\uDFA4" // 🎤 unicode 8 no microphones
+ "\uD83E\uDD42 "; // 🥂 unicode 9 clinking glasses
final byte[] unicodeBytes = unicodeString.getBytes(StandardCharsets.UTF_8);
checkSendString(
ps -> ps.setUnicodeStream(1, new ByteArrayInputStream(unicodeBytes), 16),
rs ->
assertEquals(
unicodeString.substring(0, 8),
rs.getString(1),
"expected " + unicodeString.substring(0, 8) + " but is " + rs.getString(1)),
con);
checkSendString(
ps -> ps.setBinaryStream(1, new ByteArrayInputStream(unicodeBytes)),
rs -> assertEquals(unicodeString, rs.getString(1)),
con);
checkSendString(
ps -> ps.setBinaryStream(1, new ByteArrayInputStream(unicodeBytes), 16),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setBinaryStream(1, new ByteArrayInputStream(unicodeBytes), 16L),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setCharacterStream(1, new StringReader(unicodeString)),
rs -> assertEquals(unicodeString, rs.getString(1)),
con);
checkSendString(
ps -> ps.setCharacterStream(1, new StringReader(unicodeString), 8),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setCharacterStream(1, new StringReader(unicodeString), 8L),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setNCharacterStream(1, new StringReader(unicodeString)),
rs -> assertEquals(unicodeString, rs.getString(1)),
con);
checkSendString(
ps -> ps.setNCharacterStream(1, new StringReader(unicodeString), 8),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setNCharacterStream(1, new StringReader(unicodeString), 8L),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setClob(1, new MariaDbClob(unicodeBytes, 0, 16)),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setClob(1, new StringReader(unicodeString)),
rs -> assertEquals(unicodeString, rs.getString(1)),
con);
checkSendString(
ps -> ps.setClob(1, new StringReader(unicodeString), 8),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setClob(1, new StringReader(unicodeString), 8L),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setNClob(1, new MariaDbClob(unicodeBytes, 0, 16)),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setNClob(1, new StringReader(unicodeString)),
rs -> assertEquals(unicodeString, rs.getString(1)),
con);
checkSendString(
ps -> ps.setNClob(1, new StringReader(unicodeString), 8),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setNClob(1, new StringReader(unicodeString), 8L),
rs -> assertEquals(unicodeString.substring(0, 8), rs.getString(1)),
con);
checkSendString(
ps -> ps.setURL(1, new URL("https://www.someUrl.com")),
rs -> assertEquals("https://www.someUrl.com", rs.getString(1)),
con);
// TODO SET OBJECT
}
@Test
public void checkTimeParameters() throws Exception {
checkTimeParameters(sharedConn);
checkTimeParameters(sharedConnBinary);
}
@SuppressWarnings("deprecation")
public void checkTimeParameters(Connection con) throws Exception {
Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
checkSendTime(
ps -> ps.setTime(1, new Time(Time.valueOf("18:16:01").getTime())),
rs -> assertEquals("18:16:01", rs.getTime(1).toString()),
con);
checkSendTime(
ps -> ps.setTime(1, new Time(Time.valueOf("18:16:01").getTime()), utcCal),
rs -> assertEquals("18:16:01", rs.getTime(1, utcCal).toString()),
con);
checkSendTime(
ps -> ps.setTime(1, new Time(Time.valueOf("18:16:01").getTime() + 123), utcCal),
rs ->
assertEquals(
Time.valueOf("18:16:01").getTime() + 123 - TimeZone.getDefault().getOffset(0),
rs.getTime(1).getTime()),
con);
}
@Test
public void checkNotSupported() throws Exception {
checkNotSupported(sharedConn);
checkNotSupported(sharedConnBinary);
}
private void checkNotSupported(Connection con) throws SQLException {
try (PreparedStatement preparedStatement = con.prepareStatement("SELECT * FROM prepareParam")) {
Common.assertThrowsContains(
SQLException.class,
() -> preparedStatement.setRef(1, null),
"REF parameter are not supported");
Common.assertThrowsContains(
SQLException.class,
() -> preparedStatement.setArray(1, null),
"Array parameter are not supported");
Common.assertThrowsContains(
SQLException.class,
() -> preparedStatement.setRowId(1, null),
"RowId parameter are not supported");
Common.assertThrowsContains(
SQLException.class,
() -> preparedStatement.setSQLXML(1, null),
"SQLXML parameter are not supported");
}
}
private void checkSendBlob(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam", con);
}
private void checkSendLong(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam2", con);
}
private void checkSendDouble(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam3", con);
}
private void checkSendString(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam4", con);
}
private void checkSendTimestamp(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam5", con);
}
private void checkSendTime(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
Connection con)
throws Exception {
checkSend(consumer, check, "prepareParam7", con);
}
private void checkSend(
ThrowingConsumer<PreparedStatement, Exception> consumer,
ThrowingConsumer<ResultSet, Exception> check,
String table,
Connection con)
throws Exception {
Statement stmt = con.createStatement();
stmt.execute("START TRANSACTION");
try (PreparedStatement preparedStatement =
con.prepareStatement("INSERT INTO " + table + " VALUES (?)")) {
consumer.accept(preparedStatement);
preparedStatement.execute();
}
ResultSet rs = stmt.executeQuery("SELECT * FROM " + table);
assertTrue(rs.next());
con.rollback();
check.accept(rs);
}
@Test
public void bigSend() throws SQLException {
int maxAllowedPacket = getMaxAllowedPacket();
Assumptions.assumeTrue(maxAllowedPacket > 21 * 1024 * 1024);
char[] arr = new char[20 * 1024 * 1024];
for (int pos = 0; pos < arr.length; pos++) {
arr[pos] = (char) ('A' + (pos % 60));
}
String st = new String(arr);
bigSend(sharedConn, st);
bigSend(sharedConnBinary, st);
try (Connection con = createCon("useEof=false")) {
bigSend(con, st);
}
}
public void bigSend(Connection con, String st) throws SQLException {
Statement stmt = con.createStatement();
stmt.execute("TRUNCATE bigTest");
stmt.execute("START TRANSACTION"); // if MAXSCALE ensure using WRITER
try (PreparedStatement prep = con.prepareStatement("INSERT INTO bigTest VALUES (?, ?)")) {
prep.setInt(1, 1);
prep.setString(2, st);
prep.execute();
}
ResultSet rs = stmt.executeQuery("SELECT t2 from bigTest WHERE t1 = 1");
assertTrue(rs.next());
assertEquals(st, rs.getString(1));
con.commit();
}
@Test
public void bigSendError() throws SQLException {
int maxAllowedPacket = getMaxAllowedPacket();
Assumptions.assumeTrue(maxAllowedPacket < 32 * 1024 * 1024);
char[] arr = new char[maxAllowedPacket];
for (int pos = 0; pos < arr.length; pos++) {
arr[pos] = (char) ('A' + (pos % 60));
}
boolean expectClosed = maxAllowedPacket >= 16 * 1024 * 1024;
String st = new String(arr);
try (Connection con = createCon("maxAllowedPacket=" + maxAllowedPacket)) {
bigSendError(con, st, expectClosed);
}
try (Connection con =
createCon("useServerPrepStmts=true&maxAllowedPacket=" + maxAllowedPacket)) {
bigSendError(con, st, expectClosed);
}
try (Connection con = createCon("transactionReplay&maxAllowedPacket=" + maxAllowedPacket)) {
bigSendError(con, st, expectClosed);
}
}
public void bigSendError(Connection con, String st, boolean expectClose) throws SQLException {
Statement stmt = con.createStatement();
stmt.execute("TRUNCATE bigTest");
stmt.execute("START TRANSACTION"); // if MAXSCALE ensure using WRITER
try (PreparedStatement prep = con.prepareStatement("INSERT INTO bigTest VALUES (?, ?)")) {
prep.setInt(1, 1);
prep.setString(2, st);
Common.assertThrowsContains(
SQLException.class,
prep::execute,
"Packet too big for current server max_allowed_packet value");
assertEquals(expectClose, con.isClosed());
}
if (!con.isClosed()) con.commit();
}
@Test
public void bigSendErrorMax() throws SQLException {
Assumptions.assumeTrue(
!"maxscale".equals(System.getenv("srv"))
&& !"skysql".equals(System.getenv("srv"))
&& !"skysql-ha".equals(System.getenv("srv")));
int maxAllowedPacket = getMaxAllowedPacket();
Assumptions.assumeTrue(
maxAllowedPacket > 16 * 1024 * 1024 && maxAllowedPacket < 100 * 1024 * 1024);
char[] arr = new char[maxAllowedPacket + 100];
for (int pos = 0; pos < arr.length; pos++) {
arr[pos] = (char) ('A' + (pos % 60));
}
String st = new String(arr);
try (Connection con = createCon("maxAllowedPacket=" + maxAllowedPacket)) {
bigSendErrorMax(con, st, true);
}
try (Connection con =
createCon("useServerPrepStmts=true&maxAllowedPacket=" + maxAllowedPacket)) {
bigSendErrorMax(con, st, true);
}
try (Connection con = createCon("transactionReplay&maxAllowedPacket=" + maxAllowedPacket)) {
bigSendError(con, st, true);
}
}
public void bigSendErrorMax(Connection con, String st, boolean expectClose) throws SQLException {
Statement stmt = con.createStatement();
stmt.execute("TRUNCATE bigTest");
stmt.execute("START TRANSACTION"); // if MAXSCALE ensure using WRITER
try (PreparedStatement prep = con.prepareStatement("INSERT INTO bigTest VALUES (?, ?)")) {
prep.setInt(1, 1);
prep.setString(2, st);
Common.assertThrowsContains(
SQLNonTransientConnectionException.class,
prep::execute,
"Packet too big for current server max_allowed_packet value");
assertEquals(expectClose, con.isClosed());
}
}
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
}