From 8f35a0c4c18f96ccc8c31cb779876cca026ae86e Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:48:28 +0900 Subject: [PATCH 01/11] feat(jdbc): implement setAsciiStream method in JDBC4PreparedStatement --- .../turso/jdbc4/JDBC4PreparedStatement.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 015667893..037aecd51 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -2,11 +2,13 @@ package tech.turso.jdbc4; import static java.util.Objects.requireNonNull; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.net.URL; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; @@ -164,7 +166,33 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { - // TODO + requireNonNull(this.statement); + if (x == null) { + this.statement.bindNull(parameterIndex); + return; + } + if (length < 0) { + throw new SQLException("setAsciiStream length must be non-negative"); + } + try { + byte[] buffer = new byte[length]; + int offset = 0; + while (offset < length) { + int read = x.read(buffer, offset, length - offset); + if (read == -1) { + break; + } + offset += read; + } + if (offset == 0) { + this.statement.bindNull(parameterIndex); + } else { + String ascii = new String(buffer, 0, offset, StandardCharsets.US_ASCII); + this.statement.bindText(parameterIndex, ascii); + } + } catch (IOException e) { + throw new SQLException("Error reading ASCII stream", e); + } } @Override From 11186312bd40ac1a0c1c4f88f4651cfc9cbc572d Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:14:52 +0900 Subject: [PATCH 02/11] feat(jdbc): test setAsciiStream method in JDBC4PreparedStatementTest --- .../jdbc4/JDBC4PreparedStatementTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java index 0a5c23097..459b0371a 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java @@ -3,9 +3,14 @@ package tech.turso.jdbc4; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.sql.*; import java.util.Properties; import org.junit.jupiter.api.BeforeEach; @@ -388,6 +393,70 @@ class JDBC4PreparedStatementTest { new java.math.BigDecimal(decimalText).stripTrailingZeros()); } + @Test + void testSetAsciiStream_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, 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_nullStream() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col TEXT)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + stmt.setAsciiStream(1, null, 0); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getString(1)); + } + + @Test + void testSetAsciiStream_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, 10); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getString(1)); + } + + @Test + void testSetAsciiStream_negativeLength() 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); + + assertThrows(SQLException.class, () -> stmt.setAsciiStream(1, stream, -1)); + } + @Test void execute_insert_should_return_number_of_inserted_elements() throws Exception { connection.prepareStatement("CREATE TABLE test (col INTEGER)").execute(); From 679841fc9de867764f3690e86be185dbf39b0a72 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:27:34 +0900 Subject: [PATCH 03/11] feat(jdbc): implement setBinaryStream method in JDBC4PreparedStatement --- .../turso/jdbc4/JDBC4PreparedStatement.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 037aecd51..ca7123cd7 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -24,6 +24,7 @@ import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Calendar; import tech.turso.annotations.SkipNullableCheck; import tech.turso.core.TursoResultSet; @@ -202,7 +203,33 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { - // TODO + requireNonNull(this.statement); + if (x == null) { + this.statement.bindNull(parameterIndex); + return; + } + if (length < 0) { + throw new SQLException("setBinaryStream length must be non-negative"); + } + try { + byte[] buffer = new byte[length]; + int offset = 0; + while (offset < length) { + int read = x.read(buffer, offset, length - offset); + if (read == -1) { + break; + } + offset += read; + } + if (offset == 0) { + this.statement.bindNull(parameterIndex); + } else { + byte[] actualData = Arrays.copyOf(buffer, offset); + this.statement.bindBlob(parameterIndex, actualData); + } + } catch (IOException e) { + throw new SQLException("Error reading binary stream", e); + } } @Override From 8dd666e131ee21ad97789e30d094e2287c5097b0 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:32:33 +0900 Subject: [PATCH 04/11] feat(jdbc): test setBinaryStream method in JDBC4PreparedStatementTest --- .../jdbc4/JDBC4PreparedStatementTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java index 459b0371a..8a170d622 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java @@ -418,6 +418,7 @@ class JDBC4PreparedStatementTest { connection.prepareStatement("CREATE TABLE test (col TEXT)").execute(); PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + stmt.setAsciiStream(1, null, 0); stmt.execute(); @@ -434,6 +435,7 @@ class JDBC4PreparedStatementTest { PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); InputStream empty = new ByteArrayInputStream(new byte[0]); + stmt.setAsciiStream(1, empty, 10); stmt.execute(); @@ -457,6 +459,70 @@ class JDBC4PreparedStatementTest { assertThrows(SQLException.class, () -> stmt.setAsciiStream(1, stream, -1)); } + @Test + void testSetBinaryStream_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, 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_nullStream() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col BLOB)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + + stmt.setBinaryStream(1, null, 0); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getBytes(1)); + } + + @Test + void testSetBinaryStream_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, 10); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getBytes(1)); + } + + @Test + void testSetBinaryStream_negativeLength() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col BLOB)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + + byte[] data = {1, 2, 3}; + InputStream stream = new ByteArrayInputStream(data); + + assertThrows(SQLException.class, () -> stmt.setBinaryStream(1, stream, -1)); + } + @Test void execute_insert_should_return_number_of_inserted_elements() throws Exception { connection.prepareStatement("CREATE TABLE test (col INTEGER)").execute(); From 100662d134d289b8af1e225ef5fd6542f24df252 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:45:36 +0900 Subject: [PATCH 05/11] feat(jdbc): implement setUnicodeStream method in JDBC4PreparedStatement --- .../turso/jdbc4/JDBC4PreparedStatement.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index ca7123cd7..0a698eb24 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -198,7 +198,33 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { - // TODO + requireNonNull(this.statement); + if (x == null) { + this.statement.bindNull(parameterIndex); + return; + } + if (length < 0) { + throw new SQLException("setUnicodeStream length must be non-negative"); + } + try { + byte[] buffer = new byte[length]; + int offset = 0; + while (offset < length) { + int readBytes = x.read(buffer, offset, length - offset); + if (readBytes == -1) { + break; + } + offset += readBytes; + } + if (offset == 0) { + this.statement.bindNull(parameterIndex); + } else { + String text = new String(buffer, 0, offset, StandardCharsets.UTF_8); + this.statement.bindText(parameterIndex, text); + } + } catch (IOException ioe) { + throw new SQLException("Error reading Unicode stream", ioe); + } } @Override From 52f8c1a33e9a8e4761d24df4806d96f0b9051d54 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:48:31 +0900 Subject: [PATCH 06/11] feat(jdbc): test setUnicodeStream method in JDBC4PreparedStatementTest --- .../jdbc4/JDBC4PreparedStatementTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java index 8a170d622..7c0f07321 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java @@ -523,6 +523,72 @@ class JDBC4PreparedStatementTest { assertThrows(SQLException.class, () -> stmt.setBinaryStream(1, stream, -1)); } + @Test + void testSetUnicodeStream_insert_and_select() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col TEXT)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + + String text = "안녕하세요😊 Hello🌏"; + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + InputStream stream = new ByteArrayInputStream(bytes); + + stmt.setUnicodeStream(1, stream, 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 testSetUnicodeStream_nullStream() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col TEXT)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + + stmt.setUnicodeStream(1, null, 0); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getString(1)); + } + + @Test + void testSetUnicodeStream_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.setUnicodeStream(1, empty, 10); + stmt.execute(); + + PreparedStatement stmt2 = connection.prepareStatement("SELECT col FROM test"); + ResultSet rs = stmt2.executeQuery(); + + assertTrue(rs.next()); + assertNull(rs.getString(1)); + } + + @Test + void testSetUnicodeStream_negativeLength() throws SQLException { + connection.prepareStatement("CREATE TABLE test (col TEXT)").execute(); + + PreparedStatement stmt = connection.prepareStatement("INSERT INTO test (col) VALUES (?)"); + + String text = "테스트"; + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + InputStream stream = new ByteArrayInputStream(bytes); + + assertThrows(SQLException.class, () -> stmt.setUnicodeStream(1, stream, -5)); + } + @Test void execute_insert_should_return_number_of_inserted_elements() throws Exception { connection.prepareStatement("CREATE TABLE test (col INTEGER)").execute(); From efe189c21db7b1ac367828b7ab173d261e34e705 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:39:48 +0900 Subject: [PATCH 07/11] refactor(jdbc): adjust empty stream handling and memory usage in setBinaryStream and setUnicodeStream --- .../main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 0a698eb24..17ffecc15 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -217,7 +217,7 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep offset += readBytes; } if (offset == 0) { - this.statement.bindNull(parameterIndex); + this.statement.bindText(parameterIndex, ""); } else { String text = new String(buffer, 0, offset, StandardCharsets.UTF_8); this.statement.bindText(parameterIndex, text); @@ -249,9 +249,10 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep } if (offset == 0) { this.statement.bindNull(parameterIndex); + } else if (offset == buffer.length) { + this.statement.bindBlob(parameterIndex, buffer); } else { - byte[] actualData = Arrays.copyOf(buffer, offset); - this.statement.bindBlob(parameterIndex, actualData); + this.statement.bindBlob(parameterIndex, Arrays.copyOf(buffer, offset)); } } catch (IOException e) { throw new SQLException("Error reading binary stream", e); From 0ef416704c97f875513c3c8984db5e56efeb5a24 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:47:47 +0900 Subject: [PATCH 08/11] refactor(jdbc): use ByteArrayOutputStream and handle empty streams as empty values instead of null --- .../turso/jdbc4/JDBC4PreparedStatement.java | 32 +++++++++---------- .../jdbc4/JDBC4PreparedStatementTest.java | 11 +++++-- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 17ffecc15..7fe6bf024 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -2,6 +2,7 @@ package tech.turso.jdbc4; import static java.util.Objects.requireNonNull; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -24,7 +25,6 @@ import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; -import java.util.Arrays; import java.util.Calendar; import tech.turso.annotations.SkipNullableCheck; import tech.turso.core.TursoResultSet; @@ -186,7 +186,7 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep offset += read; } if (offset == 0) { - this.statement.bindNull(parameterIndex); + this.statement.bindText(parameterIndex, ""); } else { String ascii = new String(buffer, 0, offset, StandardCharsets.US_ASCII); this.statement.bindText(parameterIndex, ascii); @@ -222,8 +222,8 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep String text = new String(buffer, 0, offset, StandardCharsets.UTF_8); this.statement.bindText(parameterIndex, text); } - } catch (IOException ioe) { - throw new SQLException("Error reading Unicode stream", ioe); + } catch (IOException e) { + throw new SQLException("Error reading Unicode stream", e); } } @@ -237,22 +237,22 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep if (length < 0) { throw new SQLException("setBinaryStream length must be non-negative"); } - try { - byte[] buffer = new byte[length]; - int offset = 0; - while (offset < length) { - int read = x.read(buffer, offset, length - offset); - if (read == -1) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[8192]; + int bytesRead; + int totalRead = 0; + while ((bytesRead = x.read(buffer, 0, Math.min(buffer.length, length - totalRead))) != -1) { + baos.write(buffer, 0, bytesRead); + totalRead += bytesRead; + if (totalRead >= length) { break; } - offset += read; } - if (offset == 0) { - this.statement.bindNull(parameterIndex); - } else if (offset == buffer.length) { - this.statement.bindBlob(parameterIndex, buffer); + byte[] data = baos.toByteArray(); + if (data.length == 0) { + this.statement.bindBlob(parameterIndex, new byte[0]); } else { - this.statement.bindBlob(parameterIndex, Arrays.copyOf(buffer, offset)); + this.statement.bindBlob(parameterIndex, data); } } catch (IOException e) { throw new SQLException("Error reading binary stream", e); diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java index 7c0f07321..939220e90 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4PreparedStatementTest.java @@ -3,6 +3,7 @@ package tech.turso.jdbc4; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -443,7 +444,7 @@ class JDBC4PreparedStatementTest { ResultSet rs = stmt2.executeQuery(); assertTrue(rs.next()); - assertNull(rs.getString(1)); + assertEquals("", rs.getString(1)); } @Test @@ -508,7 +509,11 @@ class JDBC4PreparedStatementTest { ResultSet rs = stmt2.executeQuery(); assertTrue(rs.next()); - assertNull(rs.getBytes(1)); + + byte[] result = rs.getBytes(1); + assertNotNull(result); + assertEquals(0, result.length); + assertArrayEquals(new byte[0], result); } @Test @@ -573,7 +578,7 @@ class JDBC4PreparedStatementTest { ResultSet rs = stmt2.executeQuery(); assertTrue(rs.next()); - assertNull(rs.getString(1)); + assertEquals("", rs.getString(1)); } @Test From 0323d23b0ce3658af4f66b5fdefba45e344fa122 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:33:31 +0900 Subject: [PATCH 09/11] refactor(jdbc): Added an early return when length == 0 --- .../java/tech/turso/jdbc4/JDBC4PreparedStatement.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 7fe6bf024..b0dd39734 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -237,6 +237,10 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep if (length < 0) { throw new SQLException("setBinaryStream length must be non-negative"); } + if (length == 0) { + this.statement.bindBlob(parameterIndex, new byte[0]); + return; + } try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; int bytesRead; @@ -249,11 +253,7 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep } } byte[] data = baos.toByteArray(); - if (data.length == 0) { - this.statement.bindBlob(parameterIndex, new byte[0]); - } else { - this.statement.bindBlob(parameterIndex, data); - } + this.statement.bindBlob(parameterIndex, data); } catch (IOException e) { throw new SQLException("Error reading binary stream", e); } From b75e4b5a1956fcdd29f396d3c1b554ebfbf33053 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:55:36 +0900 Subject: [PATCH 10/11] refactor(jdbc): prevent over-read and infinite loop in setBinaryStream --- .../main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index b0dd39734..7854b7763 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -245,12 +245,10 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep byte[] buffer = new byte[8192]; int bytesRead; int totalRead = 0; - while ((bytesRead = x.read(buffer, 0, Math.min(buffer.length, length - totalRead))) != -1) { + while (totalRead < length + && (bytesRead = x.read(buffer, 0, Math.min(buffer.length, length - totalRead))) > 0) { baos.write(buffer, 0, bytesRead); totalRead += bytesRead; - if (totalRead >= length) { - break; - } } byte[] data = baos.toByteArray(); this.statement.bindBlob(parameterIndex, data); From 510b8ef59fffd1fa85a0f359c6dc942dc4650b49 Mon Sep 17 00:00:00 2001 From: Orange flavored banana <106858113+moonwhistle@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:13:23 +0900 Subject: [PATCH 11/11] refactor(jdbc): Added early return for length == 0 and improved read loop condition in setUnicodeStream and setAsciiStream --- .../turso/jdbc4/JDBC4PreparedStatement.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index 7854b7763..5b38658cd 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -175,22 +175,19 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep if (length < 0) { throw new SQLException("setAsciiStream length must be non-negative"); } + if (length == 0) { + this.statement.bindText(parameterIndex, ""); + return; + } try { byte[] buffer = new byte[length]; int offset = 0; - while (offset < length) { - int read = x.read(buffer, offset, length - offset); - if (read == -1) { - break; - } + int read; + while (offset < length && (read = x.read(buffer, offset, length - offset)) > 0) { offset += read; } - if (offset == 0) { - this.statement.bindText(parameterIndex, ""); - } else { - String ascii = new String(buffer, 0, offset, StandardCharsets.US_ASCII); - this.statement.bindText(parameterIndex, ascii); - } + String ascii = new String(buffer, 0, offset, StandardCharsets.US_ASCII); + this.statement.bindText(parameterIndex, ascii); } catch (IOException e) { throw new SQLException("Error reading ASCII stream", e); } @@ -206,22 +203,19 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep if (length < 0) { throw new SQLException("setUnicodeStream length must be non-negative"); } + if (length == 0) { + this.statement.bindText(parameterIndex, ""); + return; + } try { byte[] buffer = new byte[length]; int offset = 0; - while (offset < length) { - int readBytes = x.read(buffer, offset, length - offset); - if (readBytes == -1) { - break; - } - offset += readBytes; - } - if (offset == 0) { - this.statement.bindText(parameterIndex, ""); - } else { - String text = new String(buffer, 0, offset, StandardCharsets.UTF_8); - this.statement.bindText(parameterIndex, text); + int read; + while (offset < length && (read = x.read(buffer, offset, length - offset)) > 0) { + offset += read; } + String text = new String(buffer, 0, offset, StandardCharsets.UTF_8); + this.statement.bindText(parameterIndex, text); } catch (IOException e) { throw new SQLException("Error reading Unicode stream", e); }