提交 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 ...@@ -39,6 +39,7 @@ Change Log
</li><li>Add support for DB2 "WITH UR" clause, patch from litailang </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 </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. 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> </li></ul>
<h2>Version 1.3.174 (2013-10-19)</h2> <h2>Version 1.3.174 (2013-10-19)</h2>
......
...@@ -1131,6 +1131,7 @@ or the SQL statement <code>SET MODE Oracle</code>. ...@@ -1131,6 +1131,7 @@ or the SQL statement <code>SET MODE Oracle</code>.
same values otherwise. same values otherwise.
</li><li>Concatenating <code>NULL</code> with another value </li><li>Concatenating <code>NULL</code> with another value
results in the other value. results in the other value.
</li><li>Empty strings are treated like <code>NULL</code> values.
</li></ul> </li></ul>
<h3>PostgreSQL Compatibility Mode</h3> <h3>PostgreSQL Compatibility Mode</h3>
......
...@@ -545,7 +545,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -545,7 +545,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
(compatibility with MySQL, PostgreSQL, HSQLDB; not Derby). (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>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>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>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>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. </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 { ...@@ -3153,7 +3153,7 @@ public class Parser {
} }
currentToken = "'"; currentToken = "'";
checkLiterals(true); checkLiterals(true);
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result)); currentValue = ValueString.get(StringUtils.fromCacheOrNew(result), database.getMode().treatEmptyStringsAsNull);
parseIndex = i; parseIndex = i;
currentTokenType = VALUE; currentTokenType = VALUE;
return; return;
...@@ -3167,7 +3167,7 @@ public class Parser { ...@@ -3167,7 +3167,7 @@ public class Parser {
result = sqlCommand.substring(begin, i); result = sqlCommand.substring(begin, i);
currentToken = "'"; currentToken = "'";
checkLiterals(true); checkLiterals(true);
currentValue = ValueString.get(StringUtils.fromCacheOrNew(result)); currentValue = ValueString.get(StringUtils.fromCacheOrNew(result), database.getMode().treatEmptyStringsAsNull);
parseIndex = i; parseIndex = i;
currentTokenType = VALUE; currentTokenType = VALUE;
return; return;
......
...@@ -98,6 +98,11 @@ public class Mode { ...@@ -98,6 +98,11 @@ public class Mode {
*/ */
public boolean uniqueIndexSingleNullExceptAllColumnsAreNull; public boolean uniqueIndexSingleNullExceptAllColumnsAreNull;
/**
* Empty strings are treated like NULL values. Useful for Oracle emulation.
*/
public boolean treatEmptyStringsAsNull;
/** /**
* Support the pseudo-table SYSIBM.SYSDUMMY1. * Support the pseudo-table SYSIBM.SYSDUMMY1.
*/ */
...@@ -181,6 +186,7 @@ public class Mode { ...@@ -181,6 +186,7 @@ public class Mode {
mode = new Mode("Oracle"); mode = new Mode("Oracle");
mode.aliasColumnName = true; mode.aliasColumnName = true;
mode.uniqueIndexSingleNullExceptAllColumnsAreNull = true; mode.uniqueIndexSingleNullExceptAllColumnsAreNull = true;
mode.treatEmptyStringsAsNull = true;
add(mode); add(mode);
mode = new Mode("PostgreSQL"); mode = new Mode("PostgreSQL");
......
...@@ -588,7 +588,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -588,7 +588,7 @@ public class Function extends Expression implements FunctionCall {
result = ValueLong.get(16 * length(v0)); result = ValueLong.get(16 * length(v0));
break; break;
case CHAR: case CHAR:
result = ValueString.get(String.valueOf((char) v0.getInt())); result = ValueString.get(String.valueOf((char) v0.getInt()), database.getMode().treatEmptyStringsAsNull);
break; break;
case CHAR_LENGTH: case CHAR_LENGTH:
case LENGTH: case LENGTH:
...@@ -619,30 +619,30 @@ public class Function extends Expression implements FunctionCall { ...@@ -619,30 +619,30 @@ public class Function extends Expression implements FunctionCall {
&& !StringUtils.isNullOrEmpty(tmp)) { && !StringUtils.isNullOrEmpty(tmp)) {
tmp = separator.concat(tmp); tmp = separator.concat(tmp);
} }
result = ValueString.get(result.getString().concat(tmp)); result = ValueString.get(result.getString().concat(tmp), database.getMode().treatEmptyStringsAsNull);
} }
} }
if (info.type == CONCAT_WS) { if (info.type == CONCAT_WS) {
if (separator != null && result == ValueNull.INSTANCE) { if (separator != null && result == ValueNull.INSTANCE) {
result = ValueString.get(""); result = ValueString.get("", database.getMode().treatEmptyStringsAsNull);
} }
} }
break; break;
} }
case HEXTORAW: case HEXTORAW:
result = ValueString.get(hexToRaw(v0.getString())); result = ValueString.get(hexToRaw(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case LOWER: case LOWER:
case LCASE: case LCASE:
// TODO this is locale specific, need to document or provide a way // TODO this is locale specific, need to document or provide a way
// to set the locale // to set the locale
result = ValueString.get(v0.getString().toLowerCase()); result = ValueString.get(v0.getString().toLowerCase(), database.getMode().treatEmptyStringsAsNull);
break; break;
case RAWTOHEX: case RAWTOHEX:
result = ValueString.get(rawToHex(v0.getString())); result = ValueString.get(rawToHex(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case SOUNDEX: case SOUNDEX:
result = ValueString.get(getSoundex(v0.getString())); result = ValueString.get(getSoundex(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case SPACE: { case SPACE: {
int len = Math.max(0, v0.getInt()); int len = Math.max(0, v0.getInt());
...@@ -650,39 +650,39 @@ public class Function extends Expression implements FunctionCall { ...@@ -650,39 +650,39 @@ public class Function extends Expression implements FunctionCall {
for (int i = len - 1; i >= 0; i--) { for (int i = len - 1; i >= 0; i--) {
chars[i] = ' '; chars[i] = ' ';
} }
result = ValueString.get(new String(chars)); result = ValueString.get(new String(chars), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case UPPER: case UPPER:
case UCASE: case UCASE:
// TODO this is locale specific, need to document or provide a way // TODO this is locale specific, need to document or provide a way
// to set the locale // to set the locale
result = ValueString.get(v0.getString().toUpperCase()); result = ValueString.get(v0.getString().toUpperCase(), database.getMode().treatEmptyStringsAsNull);
break; break;
case STRINGENCODE: case STRINGENCODE:
result = ValueString.get(StringUtils.javaEncode(v0.getString())); result = ValueString.get(StringUtils.javaEncode(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case STRINGDECODE: case STRINGDECODE:
result = ValueString.get(StringUtils.javaDecode(v0.getString())); result = ValueString.get(StringUtils.javaDecode(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case STRINGTOUTF8: case STRINGTOUTF8:
result = ValueBytes.getNoCopy(v0.getString().getBytes(Constants.UTF8)); result = ValueBytes.getNoCopy(v0.getString().getBytes(Constants.UTF8));
break; break;
case UTF8TOSTRING: case UTF8TOSTRING:
result = ValueString.get(new String(v0.getBytesNoCopy(), Constants.UTF8)); result = ValueString.get(new String(v0.getBytesNoCopy(), Constants.UTF8), database.getMode().treatEmptyStringsAsNull);
break; break;
case XMLCOMMENT: case XMLCOMMENT:
result = ValueString.get(StringUtils.xmlComment(v0.getString())); result = ValueString.get(StringUtils.xmlComment(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case XMLCDATA: case XMLCDATA:
result = ValueString.get(StringUtils.xmlCData(v0.getString())); result = ValueString.get(StringUtils.xmlCData(v0.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case XMLSTARTDOC: case XMLSTARTDOC:
result = ValueString.get(StringUtils.xmlStartDoc()); result = ValueString.get(StringUtils.xmlStartDoc(), database.getMode().treatEmptyStringsAsNull);
break; break;
case DAY_NAME: { case DAY_NAME: {
SimpleDateFormat dayName = new SimpleDateFormat("EEEE", Locale.ENGLISH); SimpleDateFormat dayName = new SimpleDateFormat("EEEE", Locale.ENGLISH);
result = ValueString.get(dayName.format(v0.getDate())); result = ValueString.get(dayName.format(v0.getDate()), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case DAY_OF_MONTH: case DAY_OF_MONTH:
...@@ -705,7 +705,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -705,7 +705,7 @@ public class Function extends Expression implements FunctionCall {
break; break;
case MONTH_NAME: { case MONTH_NAME: {
SimpleDateFormat monthName = new SimpleDateFormat("MMMM", Locale.ENGLISH); SimpleDateFormat monthName = new SimpleDateFormat("MMMM", Locale.ENGLISH);
result = ValueString.get(monthName.format(v0.getDate())); result = ValueString.get(monthName.format(v0.getDate()), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case QUARTER: case QUARTER:
...@@ -755,11 +755,11 @@ public class Function extends Expression implements FunctionCall { ...@@ -755,11 +755,11 @@ public class Function extends Expression implements FunctionCall {
break; break;
} }
case DATABASE: case DATABASE:
result = ValueString.get(database.getShortName()); result = ValueString.get(database.getShortName(), database.getMode().treatEmptyStringsAsNull);
break; break;
case USER: case USER:
case CURRENT_USER: case CURRENT_USER:
result = ValueString.get(session.getUser().getName()); result = ValueString.get(session.getUser().getName(), database.getMode().treatEmptyStringsAsNull);
break; break;
case IDENTITY: case IDENTITY:
result = session.getLastIdentity(); result = session.getLastIdentity();
...@@ -775,7 +775,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -775,7 +775,7 @@ public class Function extends Expression implements FunctionCall {
break; break;
case DATABASE_PATH: { case DATABASE_PATH: {
String path = database.getDatabasePath(); String path = database.getDatabasePath();
result = path == null ? (Value) ValueNull.INSTANCE : ValueString.get(path); result = path == null ? (Value) ValueNull.INSTANCE : ValueString.get(path, database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case LOCK_TIMEOUT: case LOCK_TIMEOUT:
...@@ -805,7 +805,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -805,7 +805,7 @@ public class Function extends Expression implements FunctionCall {
result = ValueInt.get(database.getLockMode()); result = ValueInt.get(database.getLockMode());
break; break;
case SCHEMA: case SCHEMA:
result = ValueString.get(session.getCurrentSchemaName()); result = ValueString.get(session.getCurrentSchemaName(), database.getMode().treatEmptyStringsAsNull);
break; break;
case SESSION_ID: case SESSION_ID:
result = ValueInt.get(session.getId()); result = ValueInt.get(session.getId());
...@@ -1108,12 +1108,12 @@ public class Function extends Expression implements FunctionCall { ...@@ -1108,12 +1108,12 @@ public class Function extends Expression implements FunctionCall {
if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) { if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) {
result = v1; result = v1;
} else { } else {
result = ValueString.get(insert(v0.getString(), v1.getInt(), v2.getInt(), v3.getString())); result = ValueString.get(insert(v0.getString(), v1.getInt(), v2.getInt(), v3.getString()), database.getMode().treatEmptyStringsAsNull);
} }
break; break;
} }
case LEFT: case LEFT:
result = ValueString.get(left(v0.getString(), v1.getInt())); result = ValueString.get(left(v0.getString(), v1.getInt()), database.getMode().treatEmptyStringsAsNull);
break; break;
case LOCATE: { case LOCATE: {
int start = v2 == null ? 0 : v2.getInt(); int start = v2 == null ? 0 : v2.getInt();
...@@ -1127,27 +1127,27 @@ public class Function extends Expression implements FunctionCall { ...@@ -1127,27 +1127,27 @@ public class Function extends Expression implements FunctionCall {
} }
case REPEAT: { case REPEAT: {
int count = Math.max(0, v1.getInt()); int count = Math.max(0, v1.getInt());
result = ValueString.get(repeat(v0.getString(), count)); result = ValueString.get(repeat(v0.getString(), count), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case REPLACE: { case REPLACE: {
String s0 = v0.getString(); String s0 = v0.getString();
String s1 = v1.getString(); String s1 = v1.getString();
String s2 = (v2 == null) ? "" : v2.getString(); String s2 = (v2 == null) ? "" : v2.getString();
result = ValueString.get(replace(s0, s1, s2)); result = ValueString.get(replace(s0, s1, s2), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case RIGHT: case RIGHT:
result = ValueString.get(right(v0.getString(), v1.getInt())); result = ValueString.get(right(v0.getString(), v1.getInt()), database.getMode().treatEmptyStringsAsNull);
break; break;
case LTRIM: case LTRIM:
result = ValueString.get(StringUtils.trim(v0.getString(), true, false, v1 == null ? " " : v1.getString())); result = ValueString.get(StringUtils.trim(v0.getString(), true, false, v1 == null ? " " : v1.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case TRIM: case TRIM:
result = ValueString.get(StringUtils.trim(v0.getString(), true, true, v1 == null ? " " : v1.getString())); result = ValueString.get(StringUtils.trim(v0.getString(), true, true, v1 == null ? " " : v1.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case RTRIM: case RTRIM:
result = ValueString.get(StringUtils.trim(v0.getString(), false, true, v1 == null ? " " : v1.getString())); result = ValueString.get(StringUtils.trim(v0.getString(), false, true, v1 == null ? " " : v1.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case SUBSTR: case SUBSTR:
case SUBSTRING: { case SUBSTRING: {
...@@ -1157,27 +1157,27 @@ public class Function extends Expression implements FunctionCall { ...@@ -1157,27 +1157,27 @@ public class Function extends Expression implements FunctionCall {
offset = s.length() + offset + 1; offset = s.length() + offset + 1;
} }
int length = v2 == null ? s.length() : v2.getInt(); int length = v2 == null ? s.length() : v2.getInt();
result = ValueString.get(substring(s, offset, length)); result = ValueString.get(substring(s, offset, length), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case POSITION: case POSITION:
result = ValueInt.get(locate(v0.getString(), v1.getString(), 0)); result = ValueInt.get(locate(v0.getString(), v1.getString(), 0));
break; break;
case XMLATTR: case XMLATTR:
result = ValueString.get(StringUtils.xmlAttr(v0.getString(), v1.getString())); result = ValueString.get(StringUtils.xmlAttr(v0.getString(), v1.getString()), database.getMode().treatEmptyStringsAsNull);
break; break;
case XMLNODE: { case XMLNODE: {
String attr = v1 == null ? null : v1 == ValueNull.INSTANCE ? null : v1.getString(); String attr = v1 == null ? null : v1 == ValueNull.INSTANCE ? null : v1.getString();
String content = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString(); String content = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString();
boolean indent = v3 == null ? true : v3.getBoolean(); boolean indent = v3 == null ? true : v3.getBoolean();
result = ValueString.get(StringUtils.xmlNode(v0.getString(), attr, content, indent)); result = ValueString.get(StringUtils.xmlNode(v0.getString(), attr, content, indent), database.getMode().treatEmptyStringsAsNull);
break; break;
} }
case REGEXP_REPLACE: { case REGEXP_REPLACE: {
String regexp = v1.getString(); String regexp = v1.getString();
String replacement = v2.getString(); String replacement = v2.getString();
try { try {
result = ValueString.get(v0.getString().replaceAll(regexp, replacement)); result = ValueString.get(v0.getString().replaceAll(regexp, replacement), database.getMode().treatEmptyStringsAsNull);
} catch (StringIndexOutOfBoundsException e) { } catch (StringIndexOutOfBoundsException e) {
throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, replacement); throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, replacement);
} catch (PatternSyntaxException e) { } catch (PatternSyntaxException e) {
...@@ -1186,13 +1186,13 @@ public class Function extends Expression implements FunctionCall { ...@@ -1186,13 +1186,13 @@ public class Function extends Expression implements FunctionCall {
break; break;
} }
case RPAD: case RPAD:
result = ValueString.get(StringUtils.pad(v0.getString(), v1.getInt(), v2 == null ? null : v2.getString(), true)); result = ValueString.get(StringUtils.pad(v0.getString(), v1.getInt(), v2 == null ? null : v2.getString(), true), database.getMode().treatEmptyStringsAsNull);
break; break;
case LPAD: case LPAD:
result = ValueString.get(StringUtils.pad(v0.getString(), v1.getInt(), v2 == null ? null : v2.getString(), false)); result = ValueString.get(StringUtils.pad(v0.getString(), v1.getInt(), v2 == null ? null : v2.getString(), false), database.getMode().treatEmptyStringsAsNull);
break; break;
case H2VERSION: case H2VERSION:
result = ValueString.get(Constants.getVersion()); result = ValueString.get(Constants.getVersion(), database.getMode().treatEmptyStringsAsNull);
break; break;
case DATE_ADD: case DATE_ADD:
result = ValueTimestamp.get(dateadd(v0.getString(), v1.getInt(), v2.getTimestamp())); result = ValueTimestamp.get(dateadd(v0.getString(), v1.getInt(), v2.getTimestamp()));
...@@ -1211,7 +1211,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1211,7 +1211,7 @@ public class Function extends Expression implements FunctionCall {
} else { } else {
String locale = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString(); String locale = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString();
String tz = v3 == null ? null : v3 == ValueNull.INSTANCE ? null : v3.getString(); String tz = v3 == null ? null : v3 == ValueNull.INSTANCE ? null : v3.getString();
result = ValueString.get(DateTimeUtils.formatDateTime(v0.getTimestamp(), v1.getString(), locale, tz)); result = ValueString.get(DateTimeUtils.formatDateTime(v0.getTimestamp(), v1.getString(), locale, tz), database.getMode().treatEmptyStringsAsNull);
} }
break; break;
} }
...@@ -1342,9 +1342,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -1342,9 +1342,9 @@ public class Function extends Expression implements FunctionCall {
} }
case XMLTEXT: case XMLTEXT:
if (v1 == null) { if (v1 == null) {
result = ValueString.get(StringUtils.xmlText(v0.getString())); result = ValueString.get(StringUtils.xmlText(v0.getString()), database.getMode().treatEmptyStringsAsNull);
} else { } else {
result = ValueString.get(StringUtils.xmlText(v0.getString(), v1.getBoolean())); result = ValueString.get(StringUtils.xmlText(v0.getString(), v1.getBoolean()), database.getMode().treatEmptyStringsAsNull);
} }
break; break;
case VALUES: case VALUES:
......
...@@ -128,15 +128,26 @@ public class ValueString extends Value { ...@@ -128,15 +128,26 @@ public class ValueString extends Value {
* @param s the string * @param s the string
* @return the value * @return the value
*/ */
public static ValueString get(String s) { public static Value get(String s) {
if (s.length() == 0) { return get(s, false);
return EMPTY; }
/**
* 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)); ValueString obj = new ValueString(StringUtils.cache(s));
if (s.length() > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { if (s.length() > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) {
return obj; return obj;
} }
return (ValueString) Value.cache(obj); return Value.cache(obj);
// this saves memory, but is really slow // this saves memory, but is really slow
// return new ValueString(s.intern()); // return new ValueString(s.intern());
} }
...@@ -148,7 +159,7 @@ public class ValueString extends Value { ...@@ -148,7 +159,7 @@ public class ValueString extends Value {
* @param s the string * @param s the string
* @return the value * @return the value
*/ */
protected ValueString getNew(String s) { protected Value getNew(String s) {
return ValueString.get(s); return ValueString.get(s);
} }
......
...@@ -45,6 +45,7 @@ import org.h2.test.db.TestMultiThread; ...@@ -45,6 +45,7 @@ import org.h2.test.db.TestMultiThread;
import org.h2.test.db.TestMultiThreadedKernel; import org.h2.test.db.TestMultiThreadedKernel;
import org.h2.test.db.TestOpenClose; import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations; import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestCompatibilityOracle;
import org.h2.test.db.TestOutOfMemory; import org.h2.test.db.TestOutOfMemory;
import org.h2.test.db.TestPowerOff; import org.h2.test.db.TestPowerOff;
import org.h2.test.db.TestQueryCache; import org.h2.test.db.TestQueryCache;
...@@ -628,6 +629,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -628,6 +629,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCheckpoint().runTest(this); new TestCheckpoint().runTest(this);
new TestCluster().runTest(this); new TestCluster().runTest(this);
new TestCompatibility().runTest(this); new TestCompatibility().runTest(this);
new TestCompatibilityOracle().runTest(this);
new TestCsv().runTest(this); new TestCsv().runTest(this);
new TestDateStorage().runTest(this); new TestDateStorage().runTest(this);
new TestDeadlock().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 ...@@ -742,6 +742,5 @@ layers waited descent spliced abstracts planning interest among sliced
lives pauses allocates kicks introduction straightforward getenv lives pauses allocates kicks introduction straightforward getenv
ordinate tweaking fetching rfe yates cookie btrfs cookies ordinate tweaking fetching rfe yates cookie btrfs cookies
nocycle nomaxvalue nominvalue cycling proceed prospective exhausted contingent 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 reverting gredler blanked koc cemo jump
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论