From 955ccd714b9b4e4fc74f56df853e5b585c9aae9e Mon Sep 17 00:00:00 2001 From: alexradzin Date: Wed, 17 Apr 2024 17:30:24 +0300 Subject: [PATCH] FIR-32016, FIR-32018: implemented blob, clob, stream related method in prepared statement --- build.gradle | 2 + .../tests/PreparedStatementTest.java | 77 ++++++++++ .../jdbc/connection/FireboltConnection.java | 1 - .../jdbc/resultset/FireboltResultSet.java | 10 +- .../FireboltPreparedStatement.java | 89 +++++------ .../firebolt/jdbc/util/InputStreamUtil.java | 16 ++ .../jdbc/resultset/FireboltResultSetTest.java | 4 +- .../FireboltPreparedStatementTest.java | 144 ++++++++++++++---- .../jdbc/util/InputStreamUtilTest.java | 16 ++ 9 files changed, 270 insertions(+), 89 deletions(-) diff --git a/build.gradle b/build.gradle index c08b00de2..85affb7f4 100644 --- a/build.gradle +++ b/build.gradle @@ -109,6 +109,8 @@ test { maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 testLogging { events 'PASSED', 'FAILED', 'SKIPPED' + exceptionFormat = 'full' + showStackTraces = true } } diff --git a/src/integrationTest/java/integration/tests/PreparedStatementTest.java b/src/integrationTest/java/integration/tests/PreparedStatementTest.java index 7cb463513..cf311b572 100644 --- a/src/integrationTest/java/integration/tests/PreparedStatementTest.java +++ b/src/integrationTest/java/integration/tests/PreparedStatementTest.java @@ -14,6 +14,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -25,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static java.sql.Statement.SUCCESS_NO_INFO; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -201,6 +208,76 @@ void shouldInsertAndSelectByteArray() throws SQLException { } } + @Test + void shouldInsertAndSelectBlobClob() throws SQLException, IOException { + Car car1 = Car.builder().make("Ford").sales(12345).signature("Henry Ford".getBytes()).build(); + Car car2 = Car.builder().make("Tesla").sales(54321).signature("Elon Musk".getBytes()).build(); + try (Connection connection = createConnection()) { + + try (PreparedStatement statement = connection + .prepareStatement("INSERT INTO prepared_statement_test (sales, make, signature) VALUES (?,?,?)")) { + statement.setLong(1, car1.getSales()); + statement.setClob(2, new SerialClob(car1.getMake().toCharArray())); + statement.setBlob(3, new SerialBlob(car1.getSignature())); + statement.addBatch(); + statement.setLong(1, car2.getSales()); + statement.setClob(2, new SerialClob(car2.getMake().toCharArray())); + statement.setBlob(3, new SerialBlob(car2.getSignature())); + statement.addBatch(); + int[] result = statement.executeBatch(); + assertArrayEquals(new int[] { SUCCESS_NO_INFO, SUCCESS_NO_INFO }, result); + } + + Set actual = new HashSet<>(); + try (Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT sales, make, signature FROM prepared_statement_test")) { + while(rs.next()) { + actual.add(Car.builder() + .sales(rs.getInt(1)) + .make(new String(new BufferedReader(rs.getClob(2).getCharacterStream()).lines().collect(Collectors.joining(System.lineSeparator())))) + .signature(rs.getBlob(3).getBinaryStream().readAllBytes()) + .build()); + } + } + assertEquals(Set.of(car1, car2), actual); + } + } + + @Test + void shouldInsertAndSelectStreams() throws SQLException, IOException { + Car car1 = Car.builder().make("Ford").sales(12345).signature("Henry Ford".getBytes()).build(); + Car car2 = Car.builder().make("Tesla").sales(54321).signature("Elon Musk".getBytes()).build(); + try (Connection connection = createConnection()) { + + try (PreparedStatement statement = connection + .prepareStatement("INSERT INTO prepared_statement_test (sales, make, signature) VALUES (?,?,?)")) { + statement.setLong(1, car1.getSales()); + statement.setCharacterStream(2, new StringReader(car1.getMake())); + statement.setBinaryStream(3, new ByteArrayInputStream(car1.getSignature())); + statement.addBatch(); + statement.setLong(1, car2.getSales()); + statement.setCharacterStream(2, new StringReader(car2.getMake())); + statement.setBinaryStream(3, new ByteArrayInputStream(car2.getSignature())); + statement.addBatch(); + int[] result = statement.executeBatch(); + assertArrayEquals(new int[] { SUCCESS_NO_INFO, SUCCESS_NO_INFO }, result); + } + + Set actual = new HashSet<>(); + try (Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT sales, make, signature FROM prepared_statement_test")) { + while(rs.next()) { + actual.add(Car.builder() + .sales(rs.getInt(1)) + .make(new String(rs.getAsciiStream(2).readAllBytes())) + .signature(rs.getBinaryStream(3).readAllBytes()) + .build()); + } + } + assertEquals(Set.of(car1, car2), actual); + } + } + private QueryResult createExpectedResult(List> expectedRows) { return QueryResult.builder().databaseName(ConnectionInfo.getInstance().getDatabase()) .tableName("prepared_statement_test") diff --git a/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java b/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java index 83c895021..e0ad33118 100644 --- a/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java +++ b/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java @@ -10,7 +10,6 @@ import com.firebolt.jdbc.exception.ExceptionType; import com.firebolt.jdbc.exception.FireboltException; import com.firebolt.jdbc.exception.FireboltSQLFeatureNotSupportedException; -import com.firebolt.jdbc.exception.FireboltUnsupportedOperationException; import com.firebolt.jdbc.metadata.FireboltDatabaseMetadata; import com.firebolt.jdbc.metadata.FireboltSystemEngineDatabaseMetadata; import com.firebolt.jdbc.service.FireboltAuthenticationService; diff --git a/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java b/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java index 24d729477..88f294e7f 100644 --- a/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java +++ b/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java @@ -8,7 +8,6 @@ import com.firebolt.jdbc.exception.ExceptionType; import com.firebolt.jdbc.exception.FireboltException; import com.firebolt.jdbc.exception.FireboltSQLFeatureNotSupportedException; -import com.firebolt.jdbc.exception.FireboltUnsupportedOperationException; import com.firebolt.jdbc.resultset.column.Column; import com.firebolt.jdbc.resultset.column.ColumnType; import com.firebolt.jdbc.resultset.compress.LZ4InputStream; @@ -53,7 +52,6 @@ import java.util.Calendar; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.TimeZone; import java.util.TreeMap; import java.util.stream.Collectors; @@ -616,17 +614,17 @@ private InputStream getTextStream(int columnIndex, Charset charset) throws SQLEx @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { - return ofNullable(getString(columnIndex)).map(String::getBytes).map(ByteArrayInputStream::new).orElse(null); + return ofNullable(getBytes(columnIndex)).map(ByteArrayInputStream::new).orElse(null); } @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { - return getBinaryStream(columnLabel); + return getAsciiStream(findColumn(columnLabel)); } @Override public InputStream getUnicodeStream(String columnLabel) throws SQLException { - return getBinaryStream(columnLabel); + return getUnicodeStream(findColumn(columnLabel)); } @Override @@ -641,7 +639,7 @@ public String getCursorName() throws SQLException { @Override public Reader getCharacterStream(int columnIndex) throws SQLException { - return ofNullable(getBinaryStream(columnIndex)).map(InputStreamReader::new).orElse(null); + return ofNullable(getUnicodeStream(columnIndex)).map(InputStreamReader::new).orElse(null); } @Override diff --git a/src/main/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatement.java b/src/main/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatement.java index 5d7691f80..5897e6e59 100644 --- a/src/main/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatement.java +++ b/src/main/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatement.java @@ -13,9 +13,11 @@ import com.firebolt.jdbc.statement.StatementUtil; import com.firebolt.jdbc.statement.rawstatement.RawStatementWrapper; import com.firebolt.jdbc.type.JavaTypeToFireboltSQLString; +import com.firebolt.jdbc.util.InputStreamUtil; import lombok.CustomLog; import lombok.NonNull; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; @@ -291,9 +293,12 @@ public boolean execute(String sql) throws SQLException { } @Override - @NotImplemented public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + try { + setString(parameterIndex, reader == null ? null : InputStreamUtil.read(reader, length)); + } catch (IOException e) { + throw new SQLException(e); + } } @Override @@ -303,15 +308,13 @@ public void setRef(int parameterIndex, Ref x) throws SQLException { } @Override - @NotImplemented - public void setBlob(int parameterIndex, Blob x) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + public void setBlob(int parameterIndex, Blob blob) throws SQLException { + setBytes(parameterIndex, blob == null ? null : blob.getBytes(1, (int)blob.length())); } @Override - @NotImplemented - public void setClob(int parameterIndex, Clob x) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + public void setClob(int parameterIndex, Clob clob) throws SQLException { + setString(parameterIndex, clob == null ? null : clob.getSubString(1, (int)clob.length())); } @Override @@ -345,33 +348,28 @@ public void setRowId(int parameterIndex, RowId x) throws SQLException { } @Override - @NotImplemented public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setCharacterStream(parameterIndex, value, length); } @Override - @NotImplemented public void setNClob(int parameterIndex, NClob value) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setClob(parameterIndex, value); } @Override - @NotImplemented public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); - } + setCharacterStream(parameterIndex, reader, length); + } @Override - @NotImplemented public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setBinaryStream(parameterIndex, inputStream, length); } @Override - @NotImplemented public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setClob(parameterIndex, reader, length); } @Override @@ -402,80 +400,75 @@ private String formatDecimalNumber(Object x, int scaleOrLength) { } @Override - @NotImplemented public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new FireboltUnsupportedOperationException(); + setBinaryStream(parameterIndex, x, length); } @Override - @NotImplemented - public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + public void setBinaryStream(int parameterIndex, InputStream inputStream, long length) throws SQLException { + setBinaryStream(parameterIndex, inputStream, (int)length); } @Override - @NotImplemented public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { - throw new FireboltUnsupportedOperationException(); + setCharacterStream(parameterIndex, reader, (int)length); } @Override - @NotImplemented public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { - throw new FireboltUnsupportedOperationException(); + setBinaryStream(parameterIndex, x); } @Override - @NotImplemented public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { - throw new FireboltUnsupportedOperationException(); + try { + setBytes(parameterIndex, x == null ? null : x.readAllBytes()); + } catch (IOException e) { + throw new SQLException(e); + } } @Override - @NotImplemented public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setCharacterStream(parameterIndex, reader, Integer.MAX_VALUE); } @Override - @NotImplemented public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setCharacterStream(parameterIndex, value); } @Override - @NotImplemented public void setClob(int parameterIndex, Reader reader) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setClob(parameterIndex, reader, Integer.MAX_VALUE); } @Override - @NotImplemented public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); - } + setBinaryStream(parameterIndex, inputStream); + } @Override - @NotImplemented public void setNClob(int parameterIndex, Reader reader) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setClob(parameterIndex, reader); } @Override - @NotImplemented public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new FireboltUnsupportedOperationException(); + setBinaryStream(parameterIndex, x, length); } @Override - @NotImplemented public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new FireboltSQLFeatureNotSupportedException(); + setBinaryStream(parameterIndex, x, length); } @Override - @NotImplemented - public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { - throw new FireboltUnsupportedOperationException(); + public void setBinaryStream(int parameterIndex, InputStream inputStream, int length) throws SQLException { + try { + setBytes(parameterIndex, inputStream == null ? null : inputStream.readNBytes(length)); + } catch (IOException e) { + throw new SQLException(e); + } } } diff --git a/src/main/java/com/firebolt/jdbc/util/InputStreamUtil.java b/src/main/java/com/firebolt/jdbc/util/InputStreamUtil.java index 3c86b9e16..8518f4a37 100644 --- a/src/main/java/com/firebolt/jdbc/util/InputStreamUtil.java +++ b/src/main/java/com/firebolt/jdbc/util/InputStreamUtil.java @@ -6,10 +6,13 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; @UtilityClass @CustomLog public class InputStreamUtil { + private static final int K_BYTE = 1024; + private static final int BUFFER_SIZE = 8 * K_BYTE; /** * Read all bytes from the input stream if the stream is not null @@ -27,4 +30,17 @@ public void readAllBytes(@Nullable InputStream is) { } } } + + public String read(Reader initialReader, int limit) throws IOException { + char[] arr = new char[BUFFER_SIZE]; + StringBuilder buffer = new StringBuilder(); + int numCharsRead; + while ((numCharsRead = initialReader.read(arr, 0, arr.length)) != -1) { + buffer.append(arr, 0, numCharsRead); + if (buffer.length() >= limit) { + break; + } + } + return buffer.length() > limit ? buffer.substring(0, limit) : buffer.toString(); + } } diff --git a/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java b/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java index 146d21481..79599f8f4 100644 --- a/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java +++ b/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java @@ -501,6 +501,8 @@ void shouldReturnBytes() throws SQLException, IOException { byte[] expected = "Taylor\\'s Prime Steak House".getBytes(); assertArrayEquals(expected, resultSet.getBytes(3)); assertArrayEquals(expected, resultSet.getBytes("name")); + assertArrayEquals(expected, resultSet.getBinaryStream(3).readAllBytes()); + assertArrayEquals(expected, resultSet.getBinaryStream("name").readAllBytes()); assertArrayEquals(expected, resultSet.getObject(3, byte[].class)); assertArrayEquals(expected, resultSet.getObject("name", byte[].class)); assertArrayEquals(expected, resultSet.getBlob(3).getBinaryStream().readAllBytes()); @@ -1236,8 +1238,6 @@ void shouldReturnStream() throws SQLException, IOException { byte[] expected = expectedStr.getBytes(); assertStream(expected, resultSet.getAsciiStream(3)); assertStream(expected, resultSet.getAsciiStream("name")); - assertStream(expected, resultSet.getBinaryStream(3)); - assertStream(expected, resultSet.getBinaryStream("name")); //noinspection deprecation assertStream(expected, resultSet.getUnicodeStream(3)); //noinspection deprecation diff --git a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java index 106c25fa7..addd6fe20 100644 --- a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java +++ b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java @@ -25,8 +25,11 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.Reader; import java.io.StringReader; import java.math.BigDecimal; import java.net.URL; @@ -75,36 +78,21 @@ class FireboltPreparedStatementTest { private final FireboltConnection connection = Mockito.mock(FireboltConnection.class); private static FireboltPreparedStatement statement; + private interface Setter { + void set(PreparedStatement ps) throws SQLException; + } + + private static class SerialNClob extends SerialClob implements NClob { + public SerialNClob(char[] ch) throws SQLException { + super(ch); + } + } + private static Stream unsupported() { return Stream.of( Arguments.of("setByte", (Executable) () -> statement.setByte(1, (byte) 127)), Arguments.of("setURL", (Executable) () -> statement.setURL(1, new URL("http://foo.bar"))), - Arguments.of("setCharacterStream", (Executable) () -> statement.setCharacterStream(1, new StringReader("hello"))), - Arguments.of("setCharacterStream(length)", (Executable) () -> statement.setCharacterStream(1, new StringReader("hello"), 2)), - Arguments.of("setCharacterStream(long length)", (Executable) () -> statement.setCharacterStream(1, new StringReader("hello"), 2L)), - Arguments.of("setNCharacterStream", (Executable) () -> statement.setNCharacterStream(1, new StringReader("hello"))), - Arguments.of("setNCharacterStream(length)", (Executable) () -> statement.setNCharacterStream(1, new StringReader("hello"), 2)), - Arguments.of("setNCharacterStream(length)", (Executable) () -> statement.setNCharacterStream(1, new StringReader("hello"), 2L)), - Arguments.of("setRef", (Executable) () -> statement.setRef(1, mock(Ref.class))), - Arguments.of("setBlob", (Executable) () -> statement.setBlob(1, mock(Blob.class))), - Arguments.of("setBlob(input stream)", (Executable) () -> statement.setBlob(1, mock(InputStream.class))), - Arguments.of("setBlob(input stream, length)", (Executable) () -> statement.setBlob(1, mock(InputStream.class), 123)), - Arguments.of("setClob", (Executable) () -> statement.setClob(1, mock(Clob.class))), - Arguments.of("setClob(reader)", (Executable) () -> statement.setClob(1, new StringReader("hello"))), - Arguments.of("setClob(reader, length)", (Executable) () -> statement.setClob(1, new StringReader("hello"), 1)), - Arguments.of("setNClob", (Executable) () -> statement.setNClob(1, mock(NClob.class))), - Arguments.of("setNClob(reader)", (Executable) () -> statement.setNClob(1, new StringReader("hello"))), - Arguments.of("setNClob(reader, length)", (Executable) () -> statement.setNClob(1, new StringReader("hello"), 1L)), - - Arguments.of("setAsciiStream", (Executable) () -> statement.setAsciiStream(1, mock(InputStream.class))), - Arguments.of("setAsciiStream(int length)", (Executable) () -> statement.setAsciiStream(1, mock(InputStream.class), 456)), - Arguments.of("setAsciiStream(long length)", (Executable) () -> statement.setAsciiStream(1, mock(InputStream.class), 456L)), - Arguments.of("setBinaryStream", (Executable) () -> statement.setBinaryStream(1, mock(InputStream.class))), - Arguments.of("setBinaryStream(int length)", (Executable) () -> statement.setBinaryStream(1, mock(InputStream.class), 456)), - Arguments.of("setBinaryStream(long length)", (Executable) () -> statement.setBinaryStream(1, mock(InputStream.class), 456L)), - Arguments.of("setUnicodeStream(int length)", (Executable) () -> statement.setUnicodeStream(1, mock(InputStream.class), 123)), - Arguments.of("setDate", (Executable) () -> statement.setDate(1, new Date(System.currentTimeMillis()), Calendar.getInstance())), Arguments.of("setTime", (Executable) () -> statement.setTime(1, new Time(System.currentTimeMillis()))), Arguments.of("setTime(calendar)", (Executable) () -> statement.setTime(1, new Time(System.currentTimeMillis()), Calendar.getInstance())), @@ -117,6 +105,89 @@ private static Stream unsupported() { ); } + private static Stream buffers() { + return Stream.of( + Arguments.of("setClob((Clob)null)", (Setter) statement -> statement.setClob(1, (Clob)null), "NULL"), + Arguments.of("setClob((Reader)null)", (Setter) statement -> statement.setClob(1, (Reader)null), "NULL"), + Arguments.of("setClob((Reader)null, length)", (Setter) statement -> statement.setClob(1, null, 1L), "NULL"), + Arguments.of("setClob(Reader)", (Setter) statement -> statement.setClob(1, new SerialClob("hello".toCharArray())), "'hello'"), + Arguments.of("setClob(Reader, length=)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 5), "'hello'"), + Arguments.of("setClob(Reader, length-1)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 4), "'hell'"), + Arguments.of("setClob(Reader, length+1)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 6), "'hello'"), + Arguments.of("setClob(Reader, 42)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 42), "'hello'"), + Arguments.of("setClob(Reader, 1)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 1), "'h'"), + Arguments.of("setClob(Reader, 0)", (Setter) statement -> statement.setClob(1, new StringReader("hello"), 0), "''"), + + Arguments.of("setNClob((NClob)null)", (Setter) statement -> statement.setNClob(1, (NClob)null), "NULL"), + Arguments.of("setNClob((Reader)null)", (Setter) statement -> statement.setNClob(1, (Reader)null), "NULL"), + Arguments.of("setNClob((Reader)null, length)", (Setter) statement -> statement.setNClob(1, null, 1L), "NULL"), + Arguments.of("setClob(Reader)", (Setter) statement -> statement.setNClob(1, new SerialNClob("hello".toCharArray())), "'hello'"), + Arguments.of("setNClob(Reader, length=)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 5), "'hello'"), + Arguments.of("setNClob(Reader, length-1)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 4), "'hell'"), + Arguments.of("setNClob(Reader, length+1)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 6), "'hello'"), + Arguments.of("setNClob(Reader, 42)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 42), "'hello'"), + Arguments.of("setNClob(Reader, 1)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 1), "'h'"), + Arguments.of("setNClob(Reader, 0)", (Setter) statement -> statement.setNClob(1, new StringReader("hello"), 0), "''"), + + Arguments.of("setBlob((Blob)null)", (Setter) statement -> statement.setBlob(1, (Blob)null), "NULL"), + Arguments.of("setBClob((InputStream)null)", (Setter) statement -> statement.setBlob(1, (InputStream)null), "NULL"), + Arguments.of("setBClob((InputStream)null, length)", (Setter) statement -> statement.setBlob(1, null, 1L), "NULL"), + Arguments.of("setBlob((Clob)null)", (Setter) statement -> statement.setBlob(1, new SerialBlob("hello".getBytes())), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + + Arguments.of("setCharacterStream(null)", (Setter) statement -> statement.setCharacterStream(1, null), "NULL"), + Arguments.of("setCharacterStream(null, int)", (Setter) statement -> statement.setCharacterStream(1, null, 1), "NULL"), + Arguments.of("setCharacterStream(null, long)", (Setter) statement -> statement.setCharacterStream(1, null, 1L), "NULL"), + Arguments.of("setCharacterStream(Reader)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello")), "'hello'"), + Arguments.of("setCharacterStream(Reader, length=)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 5), "'hello'"), + Arguments.of("setCharacterStream(Reader, length-1)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 4), "'hell'"), + Arguments.of("setCharacterStream(Reader, length+1)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 6), "'hello'"), + Arguments.of("setCharacterStream(Reader, 42)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 42), "'hello'"), + Arguments.of("setCharacterStream(Reader, 1)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 1), "'h'"), + Arguments.of("setCharacterStream(Reader, 0)", (Setter) statement -> statement.setCharacterStream(1, new StringReader("hello"), 0), "''"), + + Arguments.of("setNCharacterStream(null)", (Setter) statement -> statement.setNCharacterStream(1, null), "NULL"), + Arguments.of("setNCharacterStream(null, int)", (Setter) statement -> statement.setNCharacterStream(1, null, 1), "NULL"), + Arguments.of("setNCharacterStream(null, long)", (Setter) statement -> statement.setNCharacterStream(1, null, 1L), "NULL"), + Arguments.of("setNCharacterStream(Reader)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello")), "'hello'"), + Arguments.of("setNCharacterStream(Reader, length=)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 5), "'hello'"), + Arguments.of("setNCharacterStream(Reader, length-1)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 4), "'hell'"), + Arguments.of("setNCharacterStream(Reader, length+1)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 6), "'hello'"), + Arguments.of("setNCharacterStream(Reader, 42)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 42), "'hello'"), + Arguments.of("setNCharacterStream(Reader, 1)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 1), "'h'"), + Arguments.of("setNCharacterStream(Reader, 0)", (Setter) statement -> statement.setNCharacterStream(1, new StringReader("hello"), 0), "''"), + + Arguments.of("setAsciiStream(null)", (Setter) statement -> statement.setAsciiStream(1, null), "NULL"), + Arguments.of("setAsciiStream(null, int)", (Setter) statement -> statement.setAsciiStream(1, null, 1), "NULL"), + Arguments.of("setAsciiStream(null, long)", (Setter) statement -> statement.setAsciiStream(1, null, 1L), "NULL"), + Arguments.of("setAsciiStream(InputStream)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes())), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, length=)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 5), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, length-1)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 4), "E'\\x68\\x65\\x6c\\x6c'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, length+1)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 6), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, 42)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 42), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, 1)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 1), "E'\\x68'::BYTEA"), + Arguments.of("setAsciiStream(InputStream, 0)", (Setter) statement -> statement.setAsciiStream(1, new ByteArrayInputStream("hello".getBytes()), 0), "E'\\x'::BYTEA"), + + Arguments.of("setBinaryStream(null)", (Setter) statement -> statement.setBinaryStream(1, null), "NULL"), + Arguments.of("setBinaryStream(null, int)", (Setter) statement -> statement.setBinaryStream(1, null, 1), "NULL"), + Arguments.of("setBinaryStream(null, long)", (Setter) statement -> statement.setBinaryStream(1, null, 1L), "NULL"), + Arguments.of("setBinaryStream(InputStream)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes())), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, length=)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 5), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, length-1)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 4), "E'\\x68\\x65\\x6c\\x6c'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, length+1)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 6), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, 42)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 42), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, 1)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 1), "E'\\x68'::BYTEA"), + Arguments.of("setBinaryStream(InputStream, 0)", (Setter) statement -> statement.setBinaryStream(1, new ByteArrayInputStream("hello".getBytes()), 0), "E'\\x'::BYTEA"), + + Arguments.of("setUnicodeStream(null, int)", (Setter) statement -> statement.setUnicodeStream(1, null, 1), "NULL"), + Arguments.of("setUnicodeStream(InputStream, length=)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 5), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setUnicodeStream(InputStream, length-1)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 4), "E'\\x68\\x65\\x6c\\x6c'::BYTEA"), + Arguments.of("setUnicodeStream(InputStream, length+1)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 6), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setUnicodeStream(InputStream, 42)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 42), "E'\\x68\\x65\\x6c\\x6c\\x6f'::BYTEA"), + Arguments.of("setUnicodeStream(InputStream, 1)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 1), "E'\\x68'::BYTEA"), + Arguments.of("setUnicodeStream(InputStream, 0)", (Setter) statement -> statement.setUnicodeStream(1, new ByteArrayInputStream("hello".getBytes()), 0), "E'\\x'::BYTEA") + ); + } + @BeforeEach void beforeEach() throws SQLException { when(connection.getSessionProperties()).thenReturn(properties); @@ -187,6 +258,15 @@ void setNullByteArray() throws SQLException { queryInfoWrapperArgumentCaptor.getValue().getSql()); } + @ParameterizedTest(name = "{0}") + @MethodSource("buffers") + void setBuffer(String name, Setter setter, String expected) throws SQLException { + statement = createStatementWithSql("INSERT INTO data (field) VALUES (?)"); + setter.set(statement); + statement.execute(); + verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), anyBoolean(), any()); + assertEquals(format("INSERT INTO data (field) VALUES (%s)", expected), queryInfoWrapperArgumentCaptor.getValue().getSql()); + } @Test void shouldExecuteBatch() throws SQLException { @@ -278,13 +358,6 @@ void shouldSetBoolean() throws SQLException { assertEquals("INSERT INTO cars(available) VALUES (1)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } - @Test - void shouldThrowExceptionWhenTryingToSetCharacterStream() { - statement = createStatementWithSql("INSERT INTO cars(make) VALUES (?)"); - assertThrows(SQLFeatureNotSupportedException.class, () -> statement.setCharacterStream(1, new StringReader("hello"))); - assertThrows(SQLFeatureNotSupportedException.class, () -> statement.setCharacterStream(1, new StringReader("hello"), 2)); - } - @ParameterizedTest(name = "{0}") @MethodSource("unsupported") void shouldThrowSQLFeatureNotSupportedException(String name, Executable function) { @@ -500,6 +573,13 @@ void clearParameters() throws SQLException { statement.execute(); // now execution is successful } + @Test + void shouldThrowExceptionWhenExecutedWithSql() throws SQLException { + statement = createStatementWithSql("SELECT 1"); + statement.execute(); // should work + assertThrows(SQLException.class, () -> statement.execute("SELECT 1")); + } + private FireboltPreparedStatement createStatementWithSql(String sql) { return new FireboltPreparedStatement(fireboltStatementService, connection, sql); } diff --git a/src/test/java/com/firebolt/jdbc/util/InputStreamUtilTest.java b/src/test/java/com/firebolt/jdbc/util/InputStreamUtilTest.java index c4f1b782b..aa742bb4f 100644 --- a/src/test/java/com/firebolt/jdbc/util/InputStreamUtilTest.java +++ b/src/test/java/com/firebolt/jdbc/util/InputStreamUtilTest.java @@ -1,10 +1,13 @@ package com.firebolt.jdbc.util; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import static org.junit.jupiter.api.Assertions.*; @@ -22,4 +25,17 @@ void shouldReadAllBytes() throws IOException { void shouldNotThrowExceptionIfStreamIsNull() { assertDoesNotThrow(() -> InputStreamUtil.readAllBytes(null)); } + + @ParameterizedTest + @CsvSource(value = { + "hello,5,hello", + "hello,6,hello", + "hello,42,hello", + "hello,4,hell", + "hello,1,h", + "hello,0,''", + }) + void read(String in, int length, String expected) throws IOException { + assertEquals(expected, InputStreamUtil.read(new StringReader(in), length)); + } } \ No newline at end of file