mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-21 00:54:19 +01:00
Merge 'bindings/java: implement JDBC4 InputStream binding methods (ASCII/Binary, no-length and long overloads)' from Orange banana
## Purpose * Implement JDBC4 stream binding methods in JDBC4PreparedStatement for the following overloads: * `setAsciiStream(int, InputStream, long)`, `setAsciiStream(int, InputStream)` * `setBinaryStream(int, InputStream, long)`, `setBinaryStream(int, InputStream)` ## Changes ### In `(int, InputStream, long)` methods * Added a shared helper method `requireLengthIsPositiveInt(long length)` to validate stream length. * Validates `length` to fit SQLite’s 32-bit limit for compatibility with SQLite/Turso engines. * After validation passes, delegates to the `(int, InputStream, int)` overload for actual binding logic. ### In `(int, InputeStream)` methods - no length * Added a shared helper method `readBytes(InputStream x)`. * Reads the first byte before allocating the buffer to avoid unnecessary memory allocation for empty streams. * After reading, directly binds the data using` bindText()` (for ASCII) or `bindBlob()` (for binary). * Avoids re-wrapping the stream or reallocation since the byte array is already available. ## Related Issue * #615 Reviewed-by: Kim Seon Woo (@seonWKim) Closes #3937
This commit is contained in:
@@ -22,6 +22,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.RowId;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
import java.sql.SQLXML;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
@@ -435,12 +436,14 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep
|
||||
|
||||
@Override
|
||||
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||
// TODO
|
||||
requireLengthIsPositiveInt(length);
|
||||
setAsciiStream(parameterIndex, x, (int) length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||
// TODO
|
||||
requireLengthIsPositiveInt(length);
|
||||
setBinaryStream(parameterIndex, x, (int) length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -449,14 +452,60 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep
|
||||
// TODO
|
||||
}
|
||||
|
||||
private void requireLengthIsPositiveInt(long length) throws SQLFeatureNotSupportedException {
|
||||
if (length > Integer.MAX_VALUE || length < 0) {
|
||||
throw new SQLFeatureNotSupportedException(
|
||||
"Data must have a length between 0 and Integer.MAX_VALUE");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
|
||||
// TODO
|
||||
requireNonNull(this.statement);
|
||||
if (x == null) {
|
||||
this.statement.bindNull(parameterIndex);
|
||||
return;
|
||||
}
|
||||
byte[] data = readBytes(x);
|
||||
String ascii = new String(data, StandardCharsets.US_ASCII);
|
||||
this.statement.bindText(parameterIndex, ascii);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
|
||||
// TODO
|
||||
requireNonNull(this.statement);
|
||||
if (x == null) {
|
||||
this.statement.bindNull(parameterIndex);
|
||||
return;
|
||||
}
|
||||
byte[] data = readBytes(x);
|
||||
this.statement.bindBlob(parameterIndex, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all bytes from the given input stream.
|
||||
*
|
||||
* @param x the input stream to read
|
||||
* @return a byte array containing the data
|
||||
* @throws SQLException if an I/O error occurs while reading
|
||||
*/
|
||||
private byte[] readBytes(InputStream x) throws SQLException {
|
||||
try {
|
||||
int firstByte = x.read();
|
||||
if (firstByte == -1) {
|
||||
return new byte[0];
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write(firstByte);
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = x.read(buffer)) > 0) {
|
||||
baos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new SQLException("Error reading InputStream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -395,7 +395,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_insert_and_select() throws SQLException {
|
||||
void testSetAsciiStream_intLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -415,7 +415,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_nullStream() throws SQLException {
|
||||
void testSetAsciiStream_intLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -431,7 +431,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_emptyStream() throws SQLException {
|
||||
void testSetAsciiStream_intLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -448,7 +448,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_negativeLength() throws SQLException {
|
||||
void testSetAsciiStream_intLength_negativeLength() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -461,7 +461,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_insert_and_select() throws SQLException {
|
||||
void testSetBinaryStream_intLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -480,7 +480,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_nullStream() throws SQLException {
|
||||
void testSetBinaryStream_intLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -496,7 +496,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_emptyStream() throws SQLException {
|
||||
void testSetBinaryStream_intLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -517,7 +517,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_negativeLength() throws SQLException {
|
||||
void testSetBinaryStream_intLength_negativeLength() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -529,7 +529,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUnicodeStream_insert_and_select() throws SQLException {
|
||||
void testSetUnicodeStream_intLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -549,7 +549,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUnicodeStream_nullStream() throws SQLException {
|
||||
void testSetUnicodeStream_intLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -565,7 +565,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUnicodeStream_emptyStream() throws SQLException {
|
||||
void testSetUnicodeStream_intLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -582,7 +582,7 @@ class JDBC4PreparedStatementTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUnicodeStream_negativeLength() throws SQLException {
|
||||
void testSetUnicodeStream_intLength_negativeLength() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
@@ -594,6 +594,253 @@ class JDBC4PreparedStatementTest {
|
||||
assertThrows(SQLException.class, () -> stmt.setUnicodeStream(1, stream, -5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_longLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
String text = "test";
|
||||
byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);
|
||||
InputStream stream = new ByteArrayInputStream(bytes);
|
||||
|
||||
stmt.setAsciiStream(1, stream, (long) bytes.length);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(text, rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_longLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
stmt.setAsciiStream(1, null, 0L);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertNull(rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_longLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream empty = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
stmt.setAsciiStream(1, empty, 0L);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals("", rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_longLength_negative() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
assertThrows(SQLFeatureNotSupportedException.class, () -> stmt.setAsciiStream(1, stream, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_longLength_overflow() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
assertThrows(
|
||||
SQLFeatureNotSupportedException.class,
|
||||
() -> stmt.setAsciiStream(1, stream, (long) Integer.MAX_VALUE + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_longLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
byte[] data = {1, 2, 3, 4, 5};
|
||||
InputStream stream = new ByteArrayInputStream(data);
|
||||
|
||||
stmt.setBinaryStream(1, stream, (long) data.length);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertArrayEquals(data, rs.getBytes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_longLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
stmt.setBinaryStream(1, null, 0L);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertNull(rs.getBytes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_longLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream empty = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
stmt.setBinaryStream(1, empty, 0L);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
|
||||
byte[] result = rs.getBytes(1);
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.length);
|
||||
assertArrayEquals(new byte[0], result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_longLength_negative() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream stream = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
|
||||
assertThrows(SQLFeatureNotSupportedException.class, () -> stmt.setBinaryStream(1, stream, -1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_longLength_overflow() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
InputStream stream = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
|
||||
assertThrows(
|
||||
SQLFeatureNotSupportedException.class,
|
||||
() -> stmt.setBinaryStream(1, stream, (long) Integer.MAX_VALUE + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_noLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
String text = "test";
|
||||
byte[] bytes = text.getBytes(StandardCharsets.US_ASCII);
|
||||
InputStream stream = new ByteArrayInputStream(bytes);
|
||||
|
||||
stmt.setAsciiStream(1, stream);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertEquals(text, rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_noLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
stmt.setAsciiStream(1, null);
|
||||
stmt.execute();
|
||||
|
||||
ResultSet rs = connection.prepareStatement("SELECT col FROM test").executeQuery();
|
||||
assertTrue(rs.next());
|
||||
assertNull(rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsciiStream_noLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col TEXT)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
InputStream empty = new ByteArrayInputStream(new byte[0]);
|
||||
stmt.setAsciiStream(1, empty);
|
||||
stmt.execute();
|
||||
|
||||
ResultSet rs = connection.prepareStatement("SELECT col FROM test").executeQuery();
|
||||
assertTrue(rs.next());
|
||||
assertEquals("", rs.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_noLength_insert_and_select() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
byte[] data = {1, 2, 3, 4, 5};
|
||||
InputStream stream = new ByteArrayInputStream(data);
|
||||
|
||||
stmt.setBinaryStream(1, stream);
|
||||
stmt.execute();
|
||||
|
||||
PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test");
|
||||
ResultSet rs = stmt2.executeQuery();
|
||||
|
||||
assertTrue(rs.next());
|
||||
assertArrayEquals(data, rs.getBytes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_noLength_nullStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
stmt.setBinaryStream(1, null);
|
||||
stmt.execute();
|
||||
|
||||
ResultSet rs = connection.prepareStatement("SELECT col FROM test").executeQuery();
|
||||
assertTrue(rs.next());
|
||||
assertNull(rs.getBytes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBinaryStream_noLength_emptyStream() throws SQLException {
|
||||
connection.prepareStatement("CREATE TABLE test (col BLOB)").execute();
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)");
|
||||
|
||||
InputStream empty = new ByteArrayInputStream(new byte[0]);
|
||||
stmt.setBinaryStream(1, empty);
|
||||
stmt.execute();
|
||||
|
||||
ResultSet rs = connection.prepareStatement("SELECT col FROM test").executeQuery();
|
||||
assertTrue(rs.next());
|
||||
|
||||
byte[] result = rs.getBytes(1);
|
||||
assertNotNull(result);
|
||||
assertEquals(0, result.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_insert_should_return_number_of_inserted_elements() throws Exception {
|
||||
connection.prepareStatement("CREATE TABLE test (col INTEGER)").execute();
|
||||
|
||||
Reference in New Issue
Block a user