Skip to content

Commit db28019

Browse files
committed
Prepared statement enhancements
This change implements a number of setter methods on `PreparedStatement` that were previously throwing `SQLFeatureNotSupportedException`. `set*Stream` group of methods is implemented only for compatibility with existing tools, no actual streaming is used on DB level - full input is read to string/bytes before passing it to DB. `execute`/`executeUpdate` methods for generated keys are implemented only for cases when generated keys are not requested. `setTime/Date/Timestamp` methods with `Calendar` (time zone) support are implemented fully following the same approach used before in #166. Also the logic for `setTime` method without `Calendar` is fixed to correctly NOT use default JVM time zone. Testing: new tests added to cover implemented methods; timestamp tests are moved to separate file and time zone-specific tests are added. Fixes: #195
1 parent 2acc78b commit db28019

File tree

9 files changed

+911
-599
lines changed

9 files changed

+911
-599
lines changed

src/main/java/org/duckdb/DuckDBPreparedStatement.java

Lines changed: 136 additions & 53 deletions
Large diffs are not rendered by default.
Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
package org.duckdb;
22

33
import java.sql.Time;
4-
import java.time.LocalDate;
5-
import java.time.LocalDateTime;
4+
import java.time.*;
5+
import java.util.TimeZone;
66
import java.util.concurrent.TimeUnit;
77

88
public class DuckDBTime extends DuckDBTimestamp {
99
public DuckDBTime(Time time) {
10-
super(TimeUnit.MILLISECONDS.toMicros(time.getTime()));
10+
super(TimeUnit.MILLISECONDS.toMicros(timeToMillis(time)));
11+
}
12+
13+
public DuckDBTime(LocalTime lt) {
14+
super(TimeUnit.NANOSECONDS.toMicros(lt.toNanoOfDay()));
15+
}
16+
17+
private static long timeToMillis(Time time) {
18+
// Per JDBC spec PreparedStatement#setTime() must NOT use
19+
// default JVM time zone.
20+
// The Time we have is already shifted with the default time zone,
21+
// so we need to shift it back to keep hours/minutes/seconds
22+
// values intact.
23+
Instant instant = Instant.ofEpochMilli(time.getTime());
24+
ZoneId zoneId = TimeZone.getDefault().toZoneId();
25+
LocalDateTime ldt = LocalDateTime.ofInstant(instant, zoneId);
26+
ZonedDateTime utc = ldt.atZone(ZoneId.of("UTC"));
27+
return utc.toInstant().toEpochMilli();
1128
}
1229
}

src/main/java/org/duckdb/DuckDBTimestamp.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ public static Object valueOf(Object x) {
121121
x = new DuckDBDate((Date) x);
122122
} else if (x instanceof Time) {
123123
x = new DuckDBTime((Time) x);
124+
} else if (x instanceof LocalTime) {
125+
x = new DuckDBTime((LocalTime) x);
124126
}
125127
return x;
126128
}
Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.duckdb;
22

3-
import java.io.ByteArrayOutputStream;
4-
import java.io.IOException;
5-
import java.io.InputStream;
63
import java.sql.SQLException;
74

85
final class JdbcUtils {
@@ -15,18 +12,6 @@ static <T> T unwrap(Object obj, Class<T> iface) throws SQLException {
1512
return (T) obj;
1613
}
1714

18-
static byte[] readAllBytes(InputStream x) throws IOException {
19-
ByteArrayOutputStream out = new ByteArrayOutputStream();
20-
byte[] thing = new byte[256];
21-
int length;
22-
int offset = 0;
23-
while ((length = x.read(thing)) != -1) {
24-
out.write(thing, offset, length);
25-
offset += length;
26-
}
27-
return out.toByteArray();
28-
}
29-
3015
private JdbcUtils() {
3116
}
3217
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.duckdb.io;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.io.Reader;
7+
import java.sql.SQLException;
8+
9+
public class IOUtils {
10+
11+
public static byte[] readAllBytes(InputStream x) throws SQLException {
12+
try {
13+
ByteArrayOutputStream out = new ByteArrayOutputStream();
14+
byte[] buffer = new byte[8192];
15+
int length;
16+
while ((length = x.read(buffer)) != -1) {
17+
out.write(buffer, 0, length);
18+
}
19+
return out.toByteArray();
20+
} catch (IOException e) {
21+
throw new SQLException(e);
22+
}
23+
}
24+
25+
public static String readToString(Reader reader) throws SQLException {
26+
try {
27+
StringBuilder sb = new StringBuilder();
28+
char[] buffer = new char[4096];
29+
int length;
30+
while ((length = reader.read(buffer)) != -1) {
31+
sb.append(buffer, 0, length);
32+
}
33+
return sb.toString();
34+
} catch (IOException e) {
35+
throw new SQLException();
36+
}
37+
}
38+
39+
public static InputStream wrapStreamWithMaxBytes(InputStream is, long maxBytes) {
40+
if (maxBytes < 0) {
41+
return is;
42+
}
43+
return new LimitedInputStream(is, maxBytes);
44+
}
45+
46+
public static Reader wrapReaderWithMaxChars(Reader reader, long maxChars) {
47+
if (maxChars < 0) {
48+
return reader;
49+
}
50+
return new LimitedReader(reader, maxChars);
51+
}
52+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.duckdb.io;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
public class LimitedInputStream extends InputStream {
7+
private final InputStream source;
8+
private final long maxBytesToRead;
9+
private long bytesRead;
10+
11+
public LimitedInputStream(InputStream source, long maxBytesToRead) {
12+
if (source == null) {
13+
throw new IllegalArgumentException("Source input stream cannot be null");
14+
}
15+
if (maxBytesToRead < 0) {
16+
throw new IllegalArgumentException("maxBytesToRead must be non-negative");
17+
}
18+
this.source = source;
19+
this.maxBytesToRead = maxBytesToRead;
20+
this.bytesRead = 0;
21+
}
22+
23+
@Override
24+
public int read() throws IOException {
25+
if (bytesRead >= maxBytesToRead) {
26+
return -1; // EOF
27+
}
28+
int result = source.read();
29+
if (result != -1) {
30+
bytesRead++;
31+
}
32+
return result;
33+
}
34+
35+
@Override
36+
public int read(byte[] b, int off, int len) throws IOException {
37+
if (bytesRead >= maxBytesToRead) {
38+
return -1; // EOF
39+
}
40+
// Calculate the maximum number of bytes we can read
41+
long bytesRemaining = maxBytesToRead - bytesRead;
42+
int bytesToRead = (int) Math.min(len, bytesRemaining);
43+
int result = source.read(b, off, bytesToRead);
44+
if (result != -1) {
45+
bytesRead += result;
46+
}
47+
return result;
48+
}
49+
50+
@Override
51+
public void close() throws IOException {
52+
source.close();
53+
}
54+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.duckdb.io;
2+
3+
import java.io.IOException;
4+
import java.io.Reader;
5+
6+
public class LimitedReader extends Reader {
7+
private final Reader source;
8+
private final long maxCharsToRead;
9+
private long charsRead;
10+
11+
public LimitedReader(Reader source, long maxCharsToRead) {
12+
if (source == null) {
13+
throw new IllegalArgumentException("Source Reader cannot be null");
14+
}
15+
if (maxCharsToRead < 0) {
16+
throw new IllegalArgumentException("maxCharsToRead must be non-negative");
17+
}
18+
this.source = source;
19+
this.maxCharsToRead = maxCharsToRead;
20+
this.charsRead = 0;
21+
}
22+
23+
@Override
24+
public int read() throws IOException {
25+
if (charsRead >= maxCharsToRead) {
26+
return -1; // EOF
27+
}
28+
int result = source.read();
29+
if (result != -1) {
30+
charsRead++;
31+
}
32+
return result;
33+
}
34+
35+
@Override
36+
public int read(char[] cbuf, int off, int len) throws IOException {
37+
if (charsRead >= maxCharsToRead) {
38+
return -1; // EOF
39+
}
40+
long charsRemaining = maxCharsToRead - charsRead;
41+
int charsToRead = (int) Math.min(len, charsRemaining);
42+
int result = source.read(cbuf, off, charsToRead);
43+
if (result != -1) {
44+
charsRead += result;
45+
}
46+
return result;
47+
}
48+
49+
@Override
50+
public void close() throws IOException {
51+
source.close();
52+
}
53+
}

0 commit comments

Comments
 (0)