提交 c05e083d authored 作者: noelgrandin's avatar noelgrandin

Issue 522: Treat empty strings like NULL in Oracle compatibility mode, patch by Daniel Gredler.

上级 fbf8deed
......@@ -39,6 +39,7 @@ Change Log
</li><li>Add support for DB2 "WITH UR" clause, patch from litailang
</li><li>Added support for ON DUPLICATE KEY UPDATE like MySQL with the values() function to update with the value that
was to be inserted. Patch from Jean-Francois Noel.
</li><li>Issue 522: Treat empty strings like NULL in Oracle compatibility mode, patch by Daniel Gredler.
</li></ul>
<h2>Version 1.3.174 (2013-10-19)</h2>
......
......@@ -1131,6 +1131,7 @@ or the SQL statement <code>SET MODE Oracle</code>.
same values otherwise.
</li><li>Concatenating <code>NULL</code> with another value
results in the other value.
</li><li>Empty strings are treated like <code>NULL</code> values.
</li></ul>
<h3>PostgreSQL Compatibility Mode</h3>
......
......@@ -545,7 +545,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
(compatibility with MySQL, PostgreSQL, HSQLDB; not Derby).
</li><li>ARRAY data type: support Integer[] and so on in Java functions (currently only Object[] is supported).
</li><li>MySQL compatibility: LOCK TABLES a READ, b READ - see also http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
</li><li>Oracle compatibility: convert empty strings to null. Also convert an empty byte array to null, but not empty varray.
</li><li>The HTML to PDF converter should use http://code.google.com/p/wkhtmltopdf/
</li><li>Issue 303: automatically convert "X NOT IN(SELECT...)" to "NOT EXISTS(...)".
</li><li>MySQL compatibility: update test1 t1, test2 t2 set t1.name=t2.name where t1.id=t2.id.
......
......@@ -3153,7 +3153,7 @@ public class Parser {
}
currentToken = "'";
checkLiterals(true);
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result));
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result), database.getMode().treatEmptyStringsAsNull);
parseIndex = i;
currentTokenType = VALUE;
return;
......@@ -3167,7 +3167,7 @@ public class Parser {
result = sqlCommand.substring(begin, i);
currentToken = "'";
checkLiterals(true);
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result));
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result), database.getMode().treatEmptyStringsAsNull);
parseIndex = i;
currentTokenType = VALUE;
return;
......
......@@ -98,6 +98,11 @@ public class Mode {
*/
public boolean uniqueIndexSingleNullExceptAllColumnsAreNull;
/**
* Empty strings are treated like NULL values. Useful for Oracle emulation.
*/
public boolean treatEmptyStringsAsNull;
/**
* Support the pseudo-table SYSIBM.SYSDUMMY1.
*/
......@@ -181,6 +186,7 @@ public class Mode {
mode = new Mode("Oracle");
mode.aliasColumnName = true;
mode.uniqueIndexSingleNullExceptAllColumnsAreNull = true;
mode.treatEmptyStringsAsNull = true;
add(mode);
mode = new Mode("PostgreSQL");
......
......@@ -128,15 +128,26 @@ public class ValueString extends Value {
* @param s the string
* @return the value
*/
public static ValueString get(String s) {
if (s.length() == 0) {
return EMPTY;
public static Value get(String s) {
return get(s, false);
}
/**
* Get or create a string value for the given string.
*
* @param s the string
* @param treatEmptyStringsAsNull whether or not to treat empty strings as NULL
* @return the value
*/
public static Value get(String s, boolean treatEmptyStringsAsNull) {
if (s.isEmpty()) {
return treatEmptyStringsAsNull ? ValueNull.INSTANCE : EMPTY;
}
ValueString obj = new ValueString(StringUtils.cache(s));
if (s.length() > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) {
return obj;
}
return (ValueString) Value.cache(obj);
return Value.cache(obj);
// this saves memory, but is really slow
// return new ValueString(s.intern());
}
......@@ -148,7 +159,7 @@ public class ValueString extends Value {
* @param s the string
* @return the value
*/
protected ValueString getNew(String s) {
protected Value getNew(String s) {
return ValueString.get(s);
}
......
......@@ -45,6 +45,7 @@ import org.h2.test.db.TestMultiThread;
import org.h2.test.db.TestMultiThreadedKernel;
import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestCompatibilityOracle;
import org.h2.test.db.TestOutOfMemory;
import org.h2.test.db.TestPowerOff;
import org.h2.test.db.TestQueryCache;
......@@ -628,6 +629,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCheckpoint().runTest(this);
new TestCluster().runTest(this);
new TestCompatibility().runTest(this);
new TestCompatibilityOracle().runTest(this);
new TestCsv().runTest(this);
new TestDateStorage().runTest(this);
new TestDeadlock().runTest(this);
......
/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Arrays;
import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet;
/**
* Test Oracle compatibility mode.
*/
public class TestCompatibilityOracle extends TestBase {
/**
* Run just this test.
*
* @param s ignored
*/
public static void main(String... s) throws Exception {
TestBase test = TestBase.createCaller().init();
test.test();
}
@Override
public void test() throws Exception {
testTreatEmptyStringsAsNull();
}
private void testTreatEmptyStringsAsNull() throws SQLException {
deleteDb("oracle");
Connection conn = getConnection("oracle;MODE=Oracle");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE A (ID NUMBER, X VARCHAR2(1))");
stat.execute("INSERT INTO A VALUES (1, 'a')");
stat.execute("INSERT INTO A VALUES (2, '')");
stat.execute("INSERT INTO A VALUES (3, ' ')");
assertResult("3", stat, "SELECT COUNT(*) FROM A");
assertResult("1", stat, "SELECT COUNT(*) FROM A WHERE X IS NULL");
assertResult("2", stat, "SELECT COUNT(*) FROM A WHERE TRIM(X) IS NULL");
assertResult("0", stat, "SELECT COUNT(*) FROM A WHERE X = ''");
assertResult(new Object[][] { { 1, "a" }, { 2, null }, { 3, " " } }, stat, "SELECT * FROM A");
assertResult(new Object[][] { { 1, "a" }, { 2, null }, { 3, null } }, stat, "SELECT ID, TRIM(X) FROM A");
stat.execute("CREATE TABLE B (ID NUMBER, X NUMBER)");
stat.execute("INSERT INTO B VALUES (1, '5')");
stat.execute("INSERT INTO B VALUES (2, '')");
assertResult("2", stat, "SELECT COUNT(*) FROM B");
assertResult("1", stat, "SELECT COUNT(*) FROM B WHERE X IS NULL");
assertResult("0", stat, "SELECT COUNT(*) FROM B WHERE X = ''");
assertResult(new Object[][] { { 1, 5 }, { 2, null } }, stat, "SELECT * FROM B");
stat.execute("CREATE TABLE C (ID NUMBER, X TIMESTAMP)");
stat.execute("INSERT INTO C VALUES (1, '1979-11-12')");
stat.execute("INSERT INTO C VALUES (2, '')");
assertResult("2", stat, "SELECT COUNT(*) FROM C");
assertResult("1", stat, "SELECT COUNT(*) FROM C WHERE X IS NULL");
assertResult("0", stat, "SELECT COUNT(*) FROM C WHERE X = ''");
assertResult(new Object[][] { { 1, "1979-11-12 00:00:00.0" }, { 2, null } }, stat, "SELECT * FROM C");
stat.execute("CREATE TABLE D (ID NUMBER, X VARCHAR2(1))");
stat.execute("INSERT INTO D VALUES (1, 'a')");
stat.execute("SET @FOO = ''");
stat.execute("INSERT INTO D VALUES (2, @FOO)");
assertResult("2", stat, "SELECT COUNT(*) FROM D");
assertResult("1", stat, "SELECT COUNT(*) FROM D WHERE X IS NULL");
assertResult("0", stat, "SELECT COUNT(*) FROM D WHERE X = ''");
assertResult(new Object[][] { { 1, "a" }, { 2, null } }, stat, "SELECT * FROM D");
stat.execute("CREATE TABLE E (ID NUMBER, X RAW(1))");
stat.execute("INSERT INTO E VALUES (1, '0A')");
stat.execute("INSERT INTO E VALUES (2, '')");
assertResult("2", stat, "SELECT COUNT(*) FROM E");
assertResult("1", stat, "SELECT COUNT(*) FROM E WHERE X IS NULL");
assertResult("0", stat, "SELECT COUNT(*) FROM E WHERE X = ''");
assertResult(new Object[][] { { 1, new byte[] { 10 } }, { 2, null } }, stat, "SELECT * FROM E");
conn.close();
}
private void assertResult(Object[][] expectedRowsOfValues, Statement stat, String sql) throws SQLException {
assertResult(newSimpleResultSet(expectedRowsOfValues), stat, sql);
}
private void assertResult(ResultSet expected, Statement stat, String sql) throws SQLException {
ResultSet actual = stat.executeQuery(sql);
int expectedColumnCount = expected.getMetaData().getColumnCount();
assertEquals(expectedColumnCount, actual.getMetaData().getColumnCount());
while (true) {
boolean expectedNext = expected.next();
boolean actualNext = actual.next();
if (!expectedNext && !actualNext) {
return;
}
if (expectedNext != actualNext) {
fail("number of rows in actual and expected results sets does not match");
}
for (int i = 0; i < expectedColumnCount; i++) {
String expectedString = columnResultToString(expected.getObject(i + 1));
String actualString = columnResultToString(actual.getObject(i + 1));
assertEquals(expectedString, actualString);
}
}
}
private static String columnResultToString(Object object) {
if (object == null) {
return null;
}
if (object instanceof Object[]) {
return Arrays.deepToString(((Object[]) object));
}
if (object instanceof byte[]) {
return Arrays.toString(((byte[]) object));
}
return object.toString();
}
private static SimpleResultSet newSimpleResultSet(Object[][] rowsOfValues) {
SimpleResultSet result = new SimpleResultSet();
for (int i = 0; i < rowsOfValues[0].length; i++) {
result.addColumn(i + "", Types.JAVA_OBJECT, 0, 0);
}
for (int i = 0; i < rowsOfValues.length; i++) {
result.addRow(rowsOfValues[i]);
}
return result;
}
}
......@@ -742,6 +742,5 @@ layers waited descent spliced abstracts planning interest among sliced
lives pauses allocates kicks introduction straightforward getenv
ordinate tweaking fetching rfe yates cookie btrfs cookies
nocycle nomaxvalue nominvalue cycling proceed prospective exhausted contingent
validities hang degenerates freezes
validities hang degenerates freezes emulation gredler cemo koc blanked
reverting gredler blanked koc cemo jump
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论