Unverified 提交 24bdf676 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #772 from katzyn/JDBC

getBinaryStream() and getCharacterStream() with pos and length
...@@ -304,7 +304,7 @@ public class JdbcBlob extends TraceObject implements Blob { ...@@ -304,7 +304,7 @@ public class JdbcBlob extends TraceObject implements Blob {
} }
/** /**
* [Not supported] Returns the input stream, starting from an offset. * Returns the input stream, starting from an offset.
* *
* @param pos where to start reading * @param pos where to start reading
* @param length the number of bytes that will be read * @param length the number of bytes that will be read
...@@ -312,7 +312,13 @@ public class JdbcBlob extends TraceObject implements Blob { ...@@ -312,7 +312,13 @@ public class JdbcBlob extends TraceObject implements Blob {
*/ */
@Override @Override
public InputStream getBinaryStream(long pos, long length) throws SQLException { public InputStream getBinaryStream(long pos, long length) throws SQLException {
throw unsupported("LOB update"); try {
debugCodeCall("getBinaryStream(pos, length)");
checkClosed();
return value.getInputStream(pos, length);
} catch (Exception e) {
throw logAndConvert(e);
}
} }
private void checkClosed() { private void checkClosed() {
......
...@@ -261,11 +261,17 @@ public class JdbcClob extends TraceObject implements NClob ...@@ -261,11 +261,17 @@ public class JdbcClob extends TraceObject implements NClob
} }
/** /**
* [Not supported] Returns the reader, starting from an offset. * Returns the reader, starting from an offset.
*/ */
@Override @Override
public Reader getCharacterStream(long pos, long length) throws SQLException { public Reader getCharacterStream(long pos, long length) throws SQLException {
throw unsupported("LOB subset"); try {
debugCodeCall("getCharacterStream(pos, length)");
checkClosed();
return value.getReader(pos, length);
} catch (Exception e) {
throw logAndConvert(e);
}
} }
private void checkClosed() { private void checkClosed() {
......
package org.h2.store;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public final class RangeInputStream extends FilterInputStream {
private long offset, limit;
public RangeInputStream(InputStream in, long offset, long limit) {
super(in);
this.offset = offset;
this.limit = limit;
}
private void before() throws IOException {
while (offset > 0) {
offset -= in.skip(offset);
}
}
@Override
public int read() throws IOException {
before();
if (limit < 1) {
return -1;
}
int b = in.read();
if (b >= 0) {
limit--;
}
return b;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
before();
if (len > limit) {
len = (int) limit;
}
int cnt = in.read(b, off, len);
if (cnt > 0) {
limit -= cnt;
}
return cnt;
}
@Override
public long skip(long n) throws IOException {
before();
if (n > limit) {
n = (int) limit;
}
n = in.skip(n);
limit -= n;
return n;
}
@Override
public int available() throws IOException {
before();
int cnt = in.available();
if (cnt > limit) {
return (int) limit;
}
return cnt;
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public void mark(int readlimit) {
}
@Override
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
@Override
public boolean markSupported() {
return false;
}
}
package org.h2.store;
import java.io.IOException;
import java.io.Reader;
public final class RangeReader extends Reader {
private final Reader r;
private long offset, limit;
public RangeReader(Reader r, long offset, long limit) {
this.r = r;
this.offset = offset;
this.limit = limit;
}
private void before() throws IOException {
while (offset > 0) {
offset -= r.skip(offset);
}
}
@Override
public int read() throws IOException {
before();
if (limit < 1) {
return -1;
}
int c = r.read();
if (c >= 0) {
limit--;
}
return c;
}
@Override
public int read(char cbuf[], int off, int len) throws IOException {
before();
if (len > limit) {
len = (int) limit;
}
int cnt = r.read(cbuf, off, len);
if (cnt > 0) {
limit -= cnt;
}
return cnt;
}
@Override
public long skip(long n) throws IOException {
before();
if (n > limit) {
n = (int) limit;
}
n = r.skip(n);
limit -= n;
return n;
}
@Override
public boolean ready() throws IOException {
before();
if (limit > 0) {
return r.ready();
}
return false;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark() not supported");
}
@Override
public void reset() throws IOException {
throw new IOException("reset() not supported");
}
@Override
public void close() throws IOException {
r.close();
}
}
...@@ -19,6 +19,7 @@ import java.sql.SQLException; ...@@ -19,6 +19,7 @@ import java.sql.SQLException;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
...@@ -187,6 +188,15 @@ public abstract class Value { ...@@ -187,6 +188,15 @@ public abstract class Value {
private static final BigDecimal MIN_LONG_DECIMAL = private static final BigDecimal MIN_LONG_DECIMAL =
BigDecimal.valueOf(Long.MIN_VALUE); BigDecimal.valueOf(Long.MIN_VALUE);
private static void rangeCheck(long zeroBasedOffset, long length, long dataSize) {
if ((zeroBasedOffset | length) < 0 || length > dataSize - zeroBasedOffset) {
if (zeroBasedOffset < 0 || zeroBasedOffset > dataSize) {
throw DbException.getInvalidValueException("offset", zeroBasedOffset + 1);
}
throw DbException.getInvalidValueException("length", length);
}
}
/** /**
* Get the SQL expression for this value. * Get the SQL expression for this value.
* *
...@@ -469,10 +479,23 @@ public abstract class Value { ...@@ -469,10 +479,23 @@ public abstract class Value {
return new ByteArrayInputStream(getBytesNoCopy()); return new ByteArrayInputStream(getBytesNoCopy());
} }
public InputStream getInputStream(long oneBasedOffset, long length) {
byte[] bytes = getBytesNoCopy();
rangeCheck(--oneBasedOffset, length, bytes.length);
return new ByteArrayInputStream(bytes, /* 0-based */ (int) oneBasedOffset, (int) length);
}
public Reader getReader() { public Reader getReader() {
return new StringReader(getString()); return new StringReader(getString());
} }
public Reader getReader(long oneBasedOffset, long length) {
String string = getString();
rangeCheck(--oneBasedOffset, length, string.length());
int offset = /* 0-based */ (int) oneBasedOffset;
return new StringReader(string.substring(offset, offset + (int) length));
}
/** /**
* Add a value and return the result. * Add a value and return the result.
* *
......
...@@ -23,6 +23,8 @@ import org.h2.store.DataHandler; ...@@ -23,6 +23,8 @@ import org.h2.store.DataHandler;
import org.h2.store.FileStore; import org.h2.store.FileStore;
import org.h2.store.FileStoreInputStream; import org.h2.store.FileStoreInputStream;
import org.h2.store.FileStoreOutputStream; import org.h2.store.FileStoreOutputStream;
import org.h2.store.RangeInputStream;
import org.h2.store.RangeReader;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
...@@ -48,6 +50,25 @@ import org.h2.util.Utils; ...@@ -48,6 +50,25 @@ import org.h2.util.Utils;
*/ */
public class ValueLob extends Value { public class ValueLob extends Value {
private static void rangeCheckUnknown(long zeroBasedOffset, long length) {
if (zeroBasedOffset < 0) {
throw DbException.getInvalidValueException("offset", zeroBasedOffset + 1);
}
if (length < 0) {
throw DbException.getInvalidValueException("length", length);
}
}
static InputStream rangeInputStream(InputStream inputStream, long oneBasedOffset, long length) {
rangeCheckUnknown(--oneBasedOffset, length);
return new RangeInputStream(inputStream, /* 0-based */ oneBasedOffset, length);
}
static Reader rangeReader(Reader reader, long oneBasedOffset, long length) {
rangeCheckUnknown(--oneBasedOffset, length);
return new RangeReader(reader, /* 0-based */ oneBasedOffset, length);
}
/** /**
* This counter is used to calculate the next directory to store lobs. It is * This counter is used to calculate the next directory to store lobs. It is
* better than using a random number because less directories are created. * better than using a random number because less directories are created.
...@@ -632,6 +653,11 @@ public class ValueLob extends Value { ...@@ -632,6 +653,11 @@ public class ValueLob extends Value {
return IOUtils.getBufferedReader(getInputStream()); return IOUtils.getBufferedReader(getInputStream());
} }
@Override
public Reader getReader(long oneBasedOffset, long length) {
return rangeReader(getReader(), oneBasedOffset, length);
}
@Override @Override
public InputStream getInputStream() { public InputStream getInputStream() {
if (fileName == null) { if (fileName == null) {
...@@ -644,6 +670,14 @@ public class ValueLob extends Value { ...@@ -644,6 +670,14 @@ public class ValueLob extends Value {
Constants.IO_BUFFER_SIZE); Constants.IO_BUFFER_SIZE);
} }
@Override
public InputStream getInputStream(long oneBasedOffset, long length) {
if (fileName == null) {
return super.getInputStream(oneBasedOffset, length);
}
return rangeInputStream(getInputStream(), oneBasedOffset, length);
}
@Override @Override
public void set(PreparedStatement prep, int parameterIndex) public void set(PreparedStatement prep, int parameterIndex)
throws SQLException { throws SQLException {
......
...@@ -374,6 +374,11 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -374,6 +374,11 @@ public class ValueLobDb extends Value implements Value.ValueClob,
return IOUtils.getBufferedReader(getInputStream()); return IOUtils.getBufferedReader(getInputStream());
} }
@Override
public Reader getReader(long oneBasedOffset, long length) {
return ValueLob.rangeReader(getReader(), oneBasedOffset, length);
}
@Override @Override
public InputStream getInputStream() { public InputStream getInputStream() {
if (small != null) { if (small != null) {
...@@ -392,6 +397,14 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -392,6 +397,14 @@ public class ValueLobDb extends Value implements Value.ValueClob,
} }
} }
@Override
public InputStream getInputStream(long oneBasedOffset, long length) {
if (small != null) {
return super.getInputStream(oneBasedOffset, length);
}
return ValueLob.rangeInputStream(getInputStream(), oneBasedOffset, length);
}
@Override @Override
public void set(PreparedStatement prep, int parameterIndex) public void set(PreparedStatement prep, int parameterIndex)
throws SQLException { throws SQLException {
......
...@@ -1074,18 +1074,34 @@ public class TestLob extends TestBase { ...@@ -1074,18 +1074,34 @@ public class TestLob extends TestBase {
prep2.getQueryTimeout(); prep2.getQueryTimeout();
prep2.close(); prep2.close();
conn0.getAutoCommit(); conn0.getAutoCommit();
Reader r = clob0.getCharacterStream(); Reader r;
int ch;
r = clob0.getCharacterStream();
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
int ch = r.read(); ch = r.read();
if (ch != ('0' + (i % 10))) { if (ch != ('0' + (i % 10))) {
fail("expected " + (char) ('0' + (i % 10)) + fail("expected " + (char) ('0' + (i % 10)) +
" got: " + ch + " (" + (char) ch + ")"); " got: " + ch + " (" + (char) ch + ")");
} }
} }
int ch = r.read(); ch = r.read();
if (ch != -1) { if (ch != -1) {
fail("expected -1 got: " + ch); fail("expected -1 got: " + ch);
} }
r.close();
r = clob0.getCharacterStream(1235, 1000);
for (int i = 1234; i < 2234; i++) {
ch = r.read();
if (ch != ('0' + (i % 10))) {
fail("expected " + (char) ('0' + (i % 10)) +
" got: " + ch + " (" + (char) ch + ")");
}
}
ch = r.read();
if (ch != -1) {
fail("expected -1 got: " + ch);
}
r.close();
conn0.close(); conn0.close();
} }
......
...@@ -92,8 +92,6 @@ public class TestLobApi extends TestBase { ...@@ -92,8 +92,6 @@ public class TestLobApi extends TestBase {
position("", 0); position("", 0);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob). assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob).
position((Clob) null, 0); position((Clob) null, 0);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob).
getCharacterStream(1, 1);
Blob blob = rs.getBlob(3); Blob blob = rs.getBlob(3);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob). assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob).
...@@ -104,8 +102,6 @@ public class TestLobApi extends TestBase { ...@@ -104,8 +102,6 @@ public class TestLobApi extends TestBase {
position(new byte[1], 0); position(new byte[1], 0);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob). assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob).
position((Blob) null, 0); position((Blob) null, 0);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob).
getBinaryStream(1, 1);
assertTrue(blob.toString().endsWith("X'00'")); assertTrue(blob.toString().endsWith("X'00'"));
blob.free(); blob.free();
assertTrue(blob.toString().endsWith("null")); assertTrue(blob.toString().endsWith("null"));
......
...@@ -32,6 +32,7 @@ import java.sql.Statement; ...@@ -32,6 +32,7 @@ import java.sql.Statement;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.TimeZone; import java.util.TimeZone;
...@@ -42,6 +43,8 @@ import org.h2.test.TestBase; ...@@ -42,6 +43,8 @@ import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.LocalDateTimeUtils; import org.h2.util.LocalDateTimeUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/** /**
* Tests for the ResultSet implementation. * Tests for the ResultSet implementation.
...@@ -1548,6 +1551,9 @@ public class TestResultSet extends TestBase { ...@@ -1548,6 +1551,9 @@ public class TestResultSet extends TestBase {
stat.execute("INSERT INTO TEST VALUES(5,X'0bcec1')"); stat.execute("INSERT INTO TEST VALUES(5,X'0bcec1')");
stat.execute("INSERT INTO TEST VALUES(6,X'03030303')"); stat.execute("INSERT INTO TEST VALUES(6,X'03030303')");
stat.execute("INSERT INTO TEST VALUES(7,NULL)"); stat.execute("INSERT INTO TEST VALUES(7,NULL)");
byte[] random = new byte[0x10000];
MathUtils.randomBytes(random);
stat.execute("INSERT INTO TEST VALUES(8, X'" + StringUtils.convertBytesToHex(random) + "')");
rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" },
new int[] { Types.INTEGER, Types.BLOB }, new int[] { new int[] { Types.INTEGER, Types.BLOB }, new int[] {
...@@ -1587,14 +1593,27 @@ public class TestResultSet extends TestBase { ...@@ -1587,14 +1593,27 @@ public class TestResultSet extends TestBase {
InputStream in = rs.getBinaryStream("value"); InputStream in = rs.getBinaryStream("value");
byte[] b = readAllBytes(in); byte[] b = readAllBytes(in);
assertEqualsWithNull(new byte[] { (byte) 0x0b, (byte) 0xce, (byte) 0xc1 }, b); assertEqualsWithNull(new byte[] { (byte) 0x0b, (byte) 0xce, (byte) 0xc1 }, b);
Blob blob = rs.getObject("value", Blob.class);
try {
assertTrue(blob != null);
assertEqualsWithNull(new byte[] { (byte) 0x0b, (byte) 0xce, (byte) 0xc1 },
readAllBytes(blob.getBinaryStream()));
assertEqualsWithNull(new byte[] { (byte) 0xce,
(byte) 0xc1 }, readAllBytes(blob.getBinaryStream(2, 2)));
assertTrue(!rs.wasNull());
} finally {
blob.free();
}
assertTrue(!rs.wasNull()); assertTrue(!rs.wasNull());
rs.next(); rs.next();
Blob blob = rs.getObject("value", Blob.class); blob = rs.getObject("value", Blob.class);
try { try {
assertTrue(blob != null); assertTrue(blob != null);
assertEqualsWithNull(new byte[] { (byte) 0x03, (byte) 0x03, assertEqualsWithNull(new byte[] { (byte) 0x03, (byte) 0x03,
(byte) 0x03, (byte) 0x03 }, readAllBytes(blob.getBinaryStream())); (byte) 0x03, (byte) 0x03 }, readAllBytes(blob.getBinaryStream()));
assertEqualsWithNull(new byte[] { (byte) 0x03,
(byte) 0x03 }, readAllBytes(blob.getBinaryStream(2, 2)));
assertTrue(!rs.wasNull()); assertTrue(!rs.wasNull());
} finally { } finally {
blob.free(); blob.free();
...@@ -1603,6 +1622,20 @@ public class TestResultSet extends TestBase { ...@@ -1603,6 +1622,20 @@ public class TestResultSet extends TestBase {
assertEqualsWithNull(null, readAllBytes(rs.getBinaryStream("VaLuE"))); assertEqualsWithNull(null, readAllBytes(rs.getBinaryStream("VaLuE")));
assertTrue(rs.wasNull()); assertTrue(rs.wasNull());
rs.next();
blob = rs.getObject("value", Blob.class);
try {
assertTrue(blob != null);
assertEqualsWithNull(random, readAllBytes(blob.getBinaryStream()));
byte[] expected = Arrays.copyOfRange(random, 100, 50102);
byte[] got = readAllBytes(blob.getBinaryStream(101, 50002));
assertEqualsWithNull(expected, got);
assertTrue(!rs.wasNull());
} finally {
blob.free();
}
assertTrue(!rs.next()); assertTrue(!rs.next());
stat.execute("DROP TABLE TEST"); stat.execute("DROP TABLE TEST");
} }
...@@ -1659,6 +1692,12 @@ public class TestResultSet extends TestBase { ...@@ -1659,6 +1692,12 @@ public class TestResultSet extends TestBase {
assertTrue(!rs.wasNull()); assertTrue(!rs.wasNull());
trace(string); trace(string);
assertTrue(string != null && string.equals("Hallo")); assertTrue(string != null && string.equals("Hallo"));
Clob clob = rs.getClob(2);
try {
assertEquals("all", readString(clob.getCharacterStream(2, 3)));
} finally {
clob.free();
}
rs.next(); rs.next();
string = readString(rs.getCharacterStream("value")); string = readString(rs.getCharacterStream("value"));
...@@ -1667,7 +1706,7 @@ public class TestResultSet extends TestBase { ...@@ -1667,7 +1706,7 @@ public class TestResultSet extends TestBase {
assertTrue(string != null && string.equals("Welt!")); assertTrue(string != null && string.equals("Welt!"));
rs.next(); rs.next();
Clob clob = rs.getObject("value", Clob.class); clob = rs.getObject("value", Clob.class);
try { try {
assertTrue(clob != null); assertTrue(clob != null);
string = readString(clob.getCharacterStream()); string = readString(clob.getCharacterStream());
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论