Unverified 提交 519378ea authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1290 from katzyn/lob_comparison

Do not load the whole LOBs into memory for comparison operation
......@@ -29,6 +29,22 @@ public final class Bits {
*/
private static final VarHandle LONG_VH = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
/**
* Compare the contents of two char arrays. If the content or length of the
* first array is smaller than the second array, -1 is returned. If the content
* or length of the second array is smaller than the first array, 1 is returned.
* If the contents and lengths are the same, 0 is returned.
*
* @param data1
* the first char array (must not be null)
* @param data2
* the second char array (must not be null)
* @return the result of the comparison (-1, 1 or 0)
*/
public static int compareNotNull(char[] data1, char[] data2) {
return Integer.signum(Arrays.compare(data1, data2));
}
/**
* Compare the contents of two byte arrays. If the content or length of the
* first array is smaller than the second array, -1 is returned. If the content
......
......@@ -20,6 +20,33 @@ public final class Bits {
* h2/src/java9/precompiled/org/h2/util/Bits.class.
*/
/**
* Compare the contents of two char arrays. If the content or length of the
* first array is smaller than the second array, -1 is returned. If the content
* or length of the second array is smaller than the first array, 1 is returned.
* If the contents and lengths are the same, 0 is returned.
*
* @param data1
* the first char array (must not be null)
* @param data2
* the second char array (must not be null)
* @return the result of the comparison (-1, 1 or 0)
*/
public static int compareNotNull(char[] data1, char[] data2) {
if (data1 == data2) {
return 0;
}
int len = Math.min(data1.length, data2.length);
for (int i = 0; i < len; i++) {
char b = data1[i];
char b2 = data2[i];
if (b != b2) {
return b > b2 ? 1 : -1;
}
}
return Integer.signum(data1.length - data2.length);
}
/**
* Compare the contents of two byte arrays. If the content or length of the
* first array is smaller than the second array, -1 is returned. If the content
......
......@@ -35,6 +35,8 @@ import org.h2.util.Utils;
*/
public class ValueLob extends Value {
private static final int BLOCK_COMPARISON_SIZE = 512;
private static void rangeCheckUnknown(long zeroBasedOffset, long length) {
if (zeroBasedOffset < 0) {
throw DbException.getInvalidValueException("offset", zeroBasedOffset + 1);
......@@ -88,6 +90,90 @@ public class ValueLob extends Value {
}
}
/**
* Compares LOBs of the same type.
*
* @param v1 first LOB value
* @param v2 second LOB value
* @return result of comparison
*/
static int compare(Value v1, Value v2) {
int valueType = v1.getType();
assert valueType == v2.getType();
if (v1 instanceof ValueLobDb && v2 instanceof ValueLobDb) {
byte[] small1 = v1.getSmall(), small2 = v2.getSmall();
if (small1 != null && small2 != null) {
if (valueType == Value.BLOB) {
return Bits.compareNotNullSigned(small1, small2);
} else {
return Integer.signum(v1.getString().compareTo(v2.getString()));
}
}
}
long minPrec = Math.min(v1.getPrecision(), v2.getPrecision());
if (valueType == Value.BLOB) {
try (InputStream is1 = v1.getInputStream();
InputStream is2 = v2.getInputStream()) {
byte[] buf1 = new byte[BLOCK_COMPARISON_SIZE];
byte[] buf2 = new byte[BLOCK_COMPARISON_SIZE];
for (; minPrec >= BLOCK_COMPARISON_SIZE; minPrec -= BLOCK_COMPARISON_SIZE) {
if (IOUtils.readFully(is1, buf1, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE
|| IOUtils.readFully(is2, buf2, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE) {
throw DbException.getUnsupportedException("Invalid LOB");
}
int cmp = Bits.compareNotNullSigned(buf1, buf2);
if (cmp != 0) {
return cmp;
}
}
for (;;) {
int c1 = is1.read(), c2 = is2.read();
if (c1 < 0) {
return c2 < 0 ? 0 : -1;
}
if (c2 < 0) {
return 1;
}
if (c1 != c2) {
return Integer.compare(c1, c2);
}
}
} catch (IOException ex) {
throw DbException.convert(ex);
}
} else {
try (Reader reader1 = v1.getReader();
Reader reader2 = v2.getReader()) {
char[] buf1 = new char[BLOCK_COMPARISON_SIZE];
char[] buf2 = new char[BLOCK_COMPARISON_SIZE];
for (; minPrec >= BLOCK_COMPARISON_SIZE; minPrec -= BLOCK_COMPARISON_SIZE) {
if (IOUtils.readFully(reader1, buf1, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE
|| IOUtils.readFully(reader2, buf2, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE) {
throw DbException.getUnsupportedException("Invalid LOB");
}
int cmp = Bits.compareNotNull(buf1, buf2);
if (cmp != 0) {
return cmp;
}
}
for (;;) {
int c1 = reader1.read(), c2 = reader2.read();
if (c1 < 0) {
return c2 < 0 ? 0 : -1;
}
if (c2 < 0) {
return 1;
}
if (c1 != c2) {
return Integer.compare(c1, c2);
}
}
} catch (IOException ex) {
throw DbException.convert(ex);
}
}
}
/**
* 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.
......@@ -430,11 +516,7 @@ public class ValueLob extends Value {
@Override
protected int compareSecure(Value v, CompareMode mode) {
if (valueType == Value.CLOB) {
return Integer.signum(getString().compareTo(v.getString()));
}
byte[] v2 = v.getBytesNoCopy();
return Bits.compareNotNullSigned(getBytesNoCopy(), v2);
return compare(this, v);
}
@Override
......@@ -529,7 +611,11 @@ public class ValueLob extends Value {
@Override
public boolean equals(Object other) {
return other instanceof ValueLob && compareSecure((Value) other, null) == 0;
if (other instanceof ValueLob) {
ValueLob o = (ValueLob) other;
return valueType == o.valueType && compareSecure(o, null) == 0;
}
return false;
}
/**
......
......@@ -26,7 +26,6 @@ import org.h2.store.LobStorageFrontend;
import org.h2.store.LobStorageInterface;
import org.h2.store.RangeReader;
import org.h2.store.fs.FileUtils;
import org.h2.util.Bits;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
......@@ -386,11 +385,7 @@ public class ValueLobDb extends Value {
return 0;
}
}
if (valueType == Value.CLOB) {
return Integer.signum(getString().compareTo(v.getString()));
}
byte[] v2 = v.getBytesNoCopy();
return Bits.compareNotNullSigned(getBytesNoCopy(), v2);
return ValueLob.compare(this, v);
}
@Override
......@@ -720,7 +715,7 @@ public class ValueLobDb extends Value {
* @param small the byte array
* @return the LOB
*/
public static Value createSmallLob(int type, byte[] small) {
public static ValueLobDb createSmallLob(int type, byte[] small) {
int precision;
if (type == Value.CLOB) {
precision = new String(small, StandardCharsets.UTF_8).length();
......
......@@ -5,9 +5,13 @@
*/
package org.h2.test.unit;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
......@@ -20,7 +24,11 @@ import java.util.TimeZone;
import java.util.UUID;
import org.h2.api.ErrorCode;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.store.DataHandler;
import org.h2.test.TestBase;
import org.h2.test.TestDb;
import org.h2.test.utils.AssertThrows;
......@@ -70,6 +78,7 @@ public class TestValue extends TestDb {
testModulusDouble();
testModulusDecimal();
testModulusOperator();
testLobComparison();
}
private void testResultSetOperations() throws SQLException {
......@@ -415,4 +424,52 @@ public class TestValue extends TestDb {
}
}
private void testLobComparison() throws SQLException {
assertEquals(0, testLobComparisonImpl(null, Value.BLOB, 0, 0, 0, 0));
assertEquals(0, testLobComparisonImpl(null, Value.CLOB, 0, 0, 0, 0));
assertEquals(-1, testLobComparisonImpl(null, Value.BLOB, 1, 1, 200, 210));
assertEquals(-1, testLobComparisonImpl(null, Value.CLOB, 1, 1, 'a', 'b'));
assertEquals(1, testLobComparisonImpl(null, Value.BLOB, 512, 512, 210, 200));
assertEquals(1, testLobComparisonImpl(null, Value.CLOB, 512, 512, 'B', 'A'));
try (Connection c = DriverManager.getConnection("jdbc:h2:mem:testValue")) {
Database dh = ((Session) ((JdbcConnection) c).getSession()).getDatabase();
assertEquals(1, testLobComparisonImpl(dh, Value.BLOB, 1_024, 1_024, 210, 200));
assertEquals(1, testLobComparisonImpl(dh, Value.CLOB, 1_024, 1_024, 'B', 'A'));
assertEquals(-1, testLobComparisonImpl(dh, Value.BLOB, 10_000, 10_000, 200, 210));
assertEquals(-1, testLobComparisonImpl(dh, Value.CLOB, 10_000, 10_000, 'a', 'b'));
assertEquals(0, testLobComparisonImpl(dh, Value.BLOB, 10_000, 10_000, 0, 0));
assertEquals(0, testLobComparisonImpl(dh, Value.CLOB, 10_000, 10_000, 0, 0));
assertEquals(-1, testLobComparisonImpl(dh, Value.BLOB, 1_000, 10_000, 0, 0));
assertEquals(-1, testLobComparisonImpl(dh, Value.CLOB, 1_000, 10_000, 0, 0));
assertEquals(1, testLobComparisonImpl(dh, Value.BLOB, 10_000, 1_000, 0, 0));
assertEquals(1, testLobComparisonImpl(dh, Value.CLOB, 10_000, 1_000, 0, 0));
}
}
private static int testLobComparisonImpl(DataHandler dh, int type, int size1, int size2, int suffix1, int suffix2) {
byte[] bytes1 = new byte[size1];
byte[] bytes2 = new byte[size2];
if (size1 > 0) {
bytes1[size1 - 1] = (byte) suffix1;
}
if (size2 > 0) {
bytes2[size2 - 1] = (byte) suffix2;
}
Value lob1 = createLob(dh, type, bytes1);
Value lob2 = createLob(dh, type, bytes2);
return lob1.compareTypeSafe(lob2, null);
}
static Value createLob(DataHandler dh, int type, byte[] bytes) {
if (dh == null) {
return ValueLobDb.createSmallLob(type, bytes);
}
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
if (type == Value.BLOB) {
return dh.getLobStorage().createBlob(in, -1);
} else {
return dh.getLobStorage().createClob(new InputStreamReader(in, StandardCharsets.UTF_8), -1);
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论