提交 42dbaa81 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Handle arguments of ORA_HASH() as specified in Oracle documentation

上级 280e1972
...@@ -11,6 +11,7 @@ import java.io.InputStream; ...@@ -11,6 +11,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
...@@ -39,6 +40,7 @@ import org.h2.table.Table; ...@@ -39,6 +40,7 @@ import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.tools.CompressTool; import org.h2.tools.CompressTool;
import org.h2.tools.Csv; import org.h2.tools.Csv;
import org.h2.util.Bits;
import org.h2.util.DateTimeFunctions; import org.h2.util.DateTimeFunctions;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
...@@ -274,7 +276,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -274,7 +276,7 @@ public class Function extends Expression implements FunctionCall {
addFunction("RPAD", RPAD, VAR_ARGS, Value.STRING); addFunction("RPAD", RPAD, VAR_ARGS, Value.STRING);
addFunction("LPAD", LPAD, VAR_ARGS, Value.STRING); addFunction("LPAD", LPAD, VAR_ARGS, Value.STRING);
addFunction("TO_CHAR", TO_CHAR, VAR_ARGS, Value.STRING); addFunction("TO_CHAR", TO_CHAR, VAR_ARGS, Value.STRING);
addFunction("ORA_HASH", ORA_HASH, VAR_ARGS, Value.INT); addFunction("ORA_HASH", ORA_HASH, VAR_ARGS, Value.LONG);
addFunction("TRANSLATE", TRANSLATE, 3, Value.STRING); addFunction("TRANSLATE", TRANSLATE, 3, Value.STRING);
addFunction("REGEXP_LIKE", REGEXP_LIKE, VAR_ARGS, Value.BOOLEAN); addFunction("REGEXP_LIKE", REGEXP_LIKE, VAR_ARGS, Value.BOOLEAN);
...@@ -1372,9 +1374,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -1372,9 +1374,9 @@ public class Function extends Expression implements FunctionCall {
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
case ORA_HASH: case ORA_HASH:
result = ValueLong.get(oraHash(v0.getString(), result = oraHash(v0,
v1 == null ? null : v1.getInt(), v1 == null ? 0xffff_ffffL : v1.getLong(),
v2 == null ? null : v2.getInt())); v2 == null ? 0L : v2.getLong());
break; break;
case TO_CHAR: case TO_CHAR:
switch (v0.getType()){ switch (v0.getType()){
...@@ -1942,17 +1944,57 @@ public class Function extends Expression implements FunctionCall { ...@@ -1942,17 +1944,57 @@ public class Function extends Expression implements FunctionCall {
return new String(chars); return new String(chars);
} }
private static Integer oraHash(String s, Integer bucket, Integer seed) { private static Value oraHash(Value value, long bucket, long seed) {
int hc = s.hashCode(); if ((bucket & 0xffff_ffff_0000_0000L) != 0L) {
if (seed != null && seed.intValue() != 0) { throw DbException.getInvalidValueException("bucket", bucket);
hc *= seed.intValue() * 17;
} }
if (bucket == null || bucket.intValue() <= 0) { if ((seed & 0xffff_ffff_0000_0000L) != 0L) {
// do nothing throw DbException.getInvalidValueException("seed", seed);
} else { }
hc %= bucket.intValue(); MessageDigest md;
switch (value.getType()) {
case Value.NULL:
return ValueNull.INSTANCE;
case Value.STRING:
case Value.STRING_FIXED:
case Value.STRING_IGNORECASE:
try {
md = MessageDigest.getInstance("SHA-1");
md.update(value.getString().getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
throw DbException.convert(ex);
}
break;
case Value.BLOB:
case Value.CLOB:
try {
md = MessageDigest.getInstance("SHA-1");
byte[] buf = new byte[4096];
try (InputStream is = value.getInputStream()) {
for (int r; (r = is.read(buf)) > 0; ) {
md.update(buf, 0, r);
}
}
} catch (Exception ex) {
throw DbException.convert(ex);
}
break;
default:
try {
md = MessageDigest.getInstance("SHA-1");
md.update(value.getBytesNoCopy());
} catch (Exception ex) {
throw DbException.convert(ex);
}
}
if (seed != 0L) {
byte[] b = new byte[4];
Bits.writeInt(b, 0, (int) seed);
md.update(b);
} }
return hc; long hc = Bits.readLong(md.digest(), 0);
// Strip sign and use modulo operation to get value from 0 to bucket inclusive
return ValueLong.get((hc & Long.MAX_VALUE) % (bucket + 1));
} }
private static int makeRegexpFlags(String stringFlags) { private static int makeRegexpFlags(String stringFlags) {
......
...@@ -112,7 +112,6 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -112,7 +112,6 @@ public class TestFunctions extends TestBase implements AggregateFunction {
testNvl2(); testNvl2();
testConcatWs(); testConcatWs();
testTruncate(); testTruncate();
testOraHash();
testToCharFromDateTime(); testToCharFromDateTime();
testToCharFromNumber(); testToCharFromNumber();
testToCharFromText(); testToCharFromText();
...@@ -1280,20 +1279,6 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1280,20 +1279,6 @@ public class TestFunctions extends TestBase implements AggregateFunction {
conn.close(); conn.close();
} }
private void testOraHash() throws SQLException {
deleteDb("functions");
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
String testStr = "foo";
assertResult(String.valueOf("foo".hashCode()), stat,
String.format("SELECT ORA_HASH('%s') FROM DUAL", testStr));
assertResult(String.valueOf("foo".hashCode()), stat,
String.format("SELECT ORA_HASH('%s', 0) FROM DUAL", testStr));
assertResult(String.valueOf("foo".hashCode()), stat,
String.format("SELECT ORA_HASH('%s', 0, 0) FROM DUAL", testStr));
conn.close();
}
private void testToDateException() { private void testToDateException() {
try { try {
ToDateParser.toDate("1979-ThisWillFail-12", "YYYY-MM-DD"); ToDateParser.toDate("1979-ThisWillFail-12", "YYYY-MM-DD");
......
...@@ -128,7 +128,7 @@ public class TestScript extends TestBase { ...@@ -128,7 +128,7 @@ public class TestScript extends TestBase {
for (String s : new String[] { "ascii", "bit-length", "char", "concat", for (String s : new String[] { "ascii", "bit-length", "char", "concat",
"concat-ws", "difference", "hextoraw", "insert", "instr", "concat-ws", "difference", "hextoraw", "insert", "instr",
"left", "length", "locate", "lower", "lpad", "ltrim", "left", "length", "locate", "lower", "lpad", "ltrim",
"octet-length", "position", "rawtohex", "regexp-like", "octet-length", "ora-hash", "position", "rawtohex", "regexp-like",
"regex-replace", "repeat", "replace", "right", "rpad", "rtrim", "regex-replace", "repeat", "replace", "right", "rpad", "rtrim",
"soundex", "space", "stringdecode", "stringencode", "soundex", "space", "stringdecode", "stringencode",
"stringtoutf8", "substring", "to-char", "translate", "trim", "stringtoutf8", "substring", "to-char", "translate", "trim",
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
SELECT ORA_HASH(NULL);
>> null
SELECT ORA_HASH(NULL, 0);
>> null
SELECT ORA_HASH(NULL, 0, 0);
>> null
SELECT ORA_HASH(1);
>> 3509391659
SELECT ORA_HASH(1, -1);
> exception
SELECT ORA_HASH(1, 0);
>> 0
SELECT ORA_HASH(1, 4294967295);
>> 3509391659
SELECT ORA_HASH(1, 4294967296)
> exception
SELECT ORA_HASH(1, 4294967295, -1);
> exception
SELECT ORA_HASH(1, 4294967295, 0);
>> 3509391659
SELECT ORA_HASH(1, 4294967295, 10);
>> 2441322222
SELECT ORA_HASH(1, 4294967295, 4294967295);
>> 3501171530
SELECT ORA_HASH(1, 4294967295, 4294967296);
> exception
CREATE TABLE TEST(I BINARY, B BLOB, S VARCHAR, C CLOB);
> ok
INSERT INTO TEST VALUES ('010203', '010203', 'abc', 'abc');
> update count: 1
SELECT ORA_HASH(I) FROM TEST;
>> 2562861693
SELECT ORA_HASH(B) FROM TEST;
>> 2562861693
SELECT ORA_HASH(S) FROM TEST;
>> 1191608682
SELECT ORA_HASH(C) FROM TEST;
>> 1191608682
DROP TABLE TEST;
> ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论