提交 3fc9d62a authored 作者: noelgrandin's avatar noelgrandin

Add new collation command SET BINARY_COLLATION UNSIGNED, helps with people…

Add new collation command SET BINARY_COLLATION UNSIGNED, helps with people testing BINARY columns in MySQL mode.
上级 002ebdc9
...@@ -1041,6 +1041,20 @@ This command is effective immediately, but does not commit an open transaction. ...@@ -1041,6 +1041,20 @@ This command is effective immediately, but does not commit an open transaction.
SET CLUSTER '' SET CLUSTER ''
" "
"Commands (Other)","SET BINARY_COLLATION","
SET BINARY_COLLATION
{ UNSIGNED | SIGNED } ] }
","
Sets the collation used for comparing BINARY columns, the default is SIGNED.
This command can only be executed if there are no tables defined.
Admin rights are required to execute this command.
This command commits an open transaction.
This setting is persistent.
","
SET BINARY_COLLATION SIGNED
"
"Commands (Other)","SET COLLATION"," "Commands (Other)","SET COLLATION","
SET [ DATABASE ] COLLATION SET [ DATABASE ] COLLATION
{ OFF | collationName [ STRENGTH { PRIMARY | SECONDARY | TERTIARY | IDENTICAL } ] } { OFF | collationName [ STRENGTH { PRIMARY | SECONDARY | TERTIARY | IDENTICAL } ] }
......
...@@ -23,6 +23,7 @@ Change Log ...@@ -23,6 +23,7 @@ Change Log
the recover tool). the recover tool).
</li><li>Support TRUNC(timestamp) for improved Oracle compatiblity. </li><li>Support TRUNC(timestamp) for improved Oracle compatiblity.
</li><li>Add support for CREATE TABLE TEST (ID BIGSERIAL) for PostgreSQL compatibility. Patch from Jesse Long. </li><li>Add support for CREATE TABLE TEST (ID BIGSERIAL) for PostgreSQL compatibility. Patch from Jesse Long.
</li><li>Add new collation command SET BINARY_COLLATION UNSIGNED, helps with people testing BINARY columns in MySQL mode.
</li></ul> </li></ul>
<h2>Version 1.3.171 (2013-03-17)</h2> <h2>Version 1.3.171 (2013-03-17)</h2>
......
...@@ -4513,6 +4513,9 @@ public class Parser { ...@@ -4513,6 +4513,9 @@ public class Parser {
} else if (readIf("COLLATION")) { } else if (readIf("COLLATION")) {
readIfEqualOrTo(); readIfEqualOrTo();
return parseSetCollation(); return parseSetCollation();
} else if (readIf("BINARY_COLLATION")) {
readIfEqualOrTo();
return parseSetBinaryCollation();
} else if (readIf("CLUSTER")) { } else if (readIf("CLUSTER")) {
readIfEqualOrTo(); readIfEqualOrTo();
Set command = new Set(session, SetTypes.CLUSTER); Set command = new Set(session, SetTypes.CLUSTER);
...@@ -4684,6 +4687,17 @@ public class Parser { ...@@ -4684,6 +4687,17 @@ public class Parser {
return command; return command;
} }
private Set parseSetBinaryCollation() {
Set command = new Set(session, SetTypes.BINARY_COLLATION);
String name = readAliasIdentifier();
command.setString(name);
if (equalsToken(name, CompareMode.UNSIGNED)
|| equalsToken(name, CompareMode.SIGNED)) {
return command;
}
throw DbException.getInvalidValueException("BINARY_COLLATION", name);
}
private RunScriptCommand parseRunScript() { private RunScriptCommand parseRunScript() {
RunScriptCommand command = new RunScriptCommand(session); RunScriptCommand command = new RunScriptCommand(session);
read("FROM"); read("FROM");
......
...@@ -111,10 +111,11 @@ public class Set extends Prepared { ...@@ -111,10 +111,11 @@ public class Set extends Prepared {
if (table != null) { if (table != null) {
throw DbException.get(ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1, table.getSQL()); throw DbException.get(ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1, table.getSQL());
} }
final boolean binaryUnsigned = database.getCompareMode().isBinaryUnsigned();
CompareMode compareMode; CompareMode compareMode;
StringBuilder buff = new StringBuilder(stringValue); StringBuilder buff = new StringBuilder(stringValue);
if (stringValue.equals(CompareMode.OFF)) { if (stringValue.equals(CompareMode.OFF)) {
compareMode = CompareMode.getInstance(null, 0); compareMode = CompareMode.getInstance(null, 0, binaryUnsigned);
} else { } else {
int strength = getIntValue(); int strength = getIntValue();
buff.append(" STRENGTH "); buff.append(" STRENGTH ");
...@@ -127,12 +128,31 @@ public class Set extends Prepared { ...@@ -127,12 +128,31 @@ public class Set extends Prepared {
} else if (strength == Collator.TERTIARY) { } else if (strength == Collator.TERTIARY) {
buff.append("TERTIARY"); buff.append("TERTIARY");
} }
compareMode = CompareMode.getInstance(stringValue, strength); compareMode = CompareMode.getInstance(stringValue, strength, binaryUnsigned);
} }
addOrUpdateSetting(name, buff.toString(), 0); addOrUpdateSetting(name, buff.toString(), 0);
database.setCompareMode(compareMode); database.setCompareMode(compareMode);
break; break;
} }
case SetTypes.BINARY_COLLATION: {
session.getUser().checkAdmin();
Table table = database.getFirstUserTable();
if (table != null) {
throw DbException.get(ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1, table.getSQL());
}
CompareMode currentMode = database.getCompareMode();
CompareMode newMode;
if (stringValue.equals(CompareMode.SIGNED)) {
newMode = CompareMode.getInstance(currentMode.getName(), currentMode.getStrength(), false);
} else if (stringValue.equals(CompareMode.UNSIGNED)) {
newMode = CompareMode.getInstance(currentMode.getName(), currentMode.getStrength(), true);
} else {
throw DbException.getInvalidValueException("BINARY_COLLATION", stringValue);
}
addOrUpdateSetting(name, stringValue, 0);
database.setCompareMode(newMode);
break;
}
case SetTypes.COMPRESS_LOB: { case SetTypes.COMPRESS_LOB: {
session.getUser().checkAdmin(); session.getUser().checkAdmin();
int algo = CompressTool.getCompressAlgorithm(stringValue); int algo = CompressTool.getCompressAlgorithm(stringValue);
......
...@@ -199,6 +199,11 @@ public class SetTypes { ...@@ -199,6 +199,11 @@ public class SetTypes {
*/ */
public static final int REDO_LOG_BINARY = 37; public static final int REDO_LOG_BINARY = 37;
/**
* The type of a SET BINARY_COLLATION statement.
*/
public static final int BINARY_COLLATION = 38;
private static final ArrayList<String> TYPES = New.arrayList(); private static final ArrayList<String> TYPES = New.arrayList();
private SetTypes() { private SetTypes() {
...@@ -245,6 +250,7 @@ public class SetTypes { ...@@ -245,6 +250,7 @@ public class SetTypes {
list.add(VARIABLE, "@"); list.add(VARIABLE, "@");
list.add(QUERY_TIMEOUT, "QUERY_TIMEOUT"); list.add(QUERY_TIMEOUT, "QUERY_TIMEOUT");
list.add(REDO_LOG_BINARY, "REDO_LOG_BINARY"); list.add(REDO_LOG_BINARY, "REDO_LOG_BINARY");
list.add(BINARY_COLLATION, "BINARY_COLLATION");
} }
/** /**
......
...@@ -178,7 +178,7 @@ public class Database implements DataHandler { ...@@ -178,7 +178,7 @@ public class Database implements DataHandler {
String name = ci.getName(); String name = ci.getName();
this.dbSettings = ci.getDbSettings(); this.dbSettings = ci.getDbSettings();
this.reconnectCheckDelay = dbSettings.reconnectCheckDelay; this.reconnectCheckDelay = dbSettings.reconnectCheckDelay;
this.compareMode = CompareMode.getInstance(null, 0); this.compareMode = CompareMode.getInstance(null, 0, false);
this.persistent = ci.isPersistent(); this.persistent = ci.isPersistent();
this.filePasswordHash = ci.getFilePasswordHash(); this.filePasswordHash = ci.getFilePasswordHash();
this.databaseName = name; this.databaseName = name;
......
...@@ -82,7 +82,7 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -82,7 +82,7 @@ public class JdbcConnection extends TraceObject implements Connection {
private int savepointId; private int savepointId;
private String catalog; private String catalog;
private Statement executingStatement; private Statement executingStatement;
private final CompareMode compareMode = CompareMode.getInstance(null, 0); private final CompareMode compareMode = CompareMode.getInstance(null, 0, false);
private final CloseWatcher watcher; private final CloseWatcher watcher;
private int queryTimeoutCache = -1; private int queryTimeoutCache = -1;
......
...@@ -1610,10 +1610,9 @@ public class PageStore implements CacheWriter { ...@@ -1610,10 +1610,9 @@ public class PageStore implements CacheWriter {
int type = row.getValue(1).getInt(); int type = row.getValue(1).getInt();
int parent = row.getValue(2).getInt(); int parent = row.getValue(2).getInt();
int rootPageId = row.getValue(3).getInt(); int rootPageId = row.getValue(3).getInt();
String options = row.getValue(4).getString(); String[] options = StringUtils.arraySplit(row.getValue(4).getString(), ',', false);
String columnList = row.getValue(5).getString(); String columnList = row.getValue(5).getString();
String[] columns = StringUtils.arraySplit(columnList, ',', false); String[] columns = StringUtils.arraySplit(columnList, ',', false);
String[] ops = StringUtils.arraySplit(options, ',', false);
Index meta; Index meta;
if (trace.isDebugEnabled()) { if (trace.isDebugEnabled()) {
trace.debug("addMeta id="+ id +" type=" + type + trace.debug("addMeta id="+ id +" type=" + type +
...@@ -1639,13 +1638,17 @@ public class PageStore implements CacheWriter { ...@@ -1639,13 +1638,17 @@ public class PageStore implements CacheWriter {
data.schema = metaSchema; data.schema = metaSchema;
data.tableName = "T" + id; data.tableName = "T" + id;
data.id = id; data.id = id;
data.temporary = ops[2].equals("temp"); data.temporary = options[2].equals("temp");
data.persistData = true; data.persistData = true;
data.persistIndexes = true; data.persistIndexes = true;
data.create = false; data.create = false;
data.session = session; data.session = session;
RegularTable table = new RegularTable(data); RegularTable table = new RegularTable(data);
CompareMode mode = CompareMode.getInstance(ops[0], Integer.parseInt(ops[1])); boolean binaryUnsigned = false;
if (options.length > 3) {
binaryUnsigned = Boolean.parseBoolean(options[3]);
}
CompareMode mode = CompareMode.getInstance(options[0], Integer.parseInt(options[1]), binaryUnsigned);
table.setCompareMode(mode); table.setCompareMode(mode);
meta = table.getScanIndex(session); meta = table.getScanIndex(session);
} else { } else {
...@@ -1671,7 +1674,7 @@ public class PageStore implements CacheWriter { ...@@ -1671,7 +1674,7 @@ public class PageStore implements CacheWriter {
cols[i] = ic; cols[i] = ic;
} }
IndexType indexType; IndexType indexType;
if (ops[3].equals("d")) { if (options[3].equals("d")) {
indexType = IndexType.createPrimaryKey(true, false); indexType = IndexType.createPrimaryKey(true, false);
Column[] tableColumns = table.getColumns(); Column[] tableColumns = table.getColumns();
for (IndexColumn indexColumn : cols) { for (IndexColumn indexColumn : cols) {
...@@ -1743,6 +1746,7 @@ public class PageStore implements CacheWriter { ...@@ -1743,6 +1746,7 @@ public class PageStore implements CacheWriter {
if (index instanceof PageDelegateIndex) { if (index instanceof PageDelegateIndex) {
options += "d"; options += "d";
} }
options += "," + mode.isBinaryUnsigned();
Row row = metaTable.getTemplateRow(); Row row = metaTable.getTemplateRow();
row.setValue(0, ValueInt.get(index.getId())); row.setValue(0, ValueInt.get(index.getId()));
row.setValue(1, ValueInt.get(type)); row.setValue(1, ValueInt.get(type));
......
...@@ -228,6 +228,33 @@ public class Utils { ...@@ -228,6 +228,33 @@ public class Utils {
return Integer.signum(data1.length - data2.length); 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 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.
* <p>
* This method interprets bytes as unsigned.
*
* @param data1 the first byte array (must not be null)
* @param data2 the second byte array (must not be null)
* @return the result of the comparison (-1, 1 or 0)
*/
public static int compareNotNullUnsigned(byte[] data1, byte[] data2) {
if (data1 == data2) {
return 0;
}
int len = Math.min(data1.length, data2.length);
for (int i = 0; i < len; i++) {
int b = data1[i] & 0xff;
int b2 = data2[i] & 0xff;
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 * 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 * first array is smaller than the second array, -1 is returned. If the
......
...@@ -34,6 +34,16 @@ public class CompareMode { ...@@ -34,6 +34,16 @@ public class CompareMode {
*/ */
public static final String ICU4J = "ICU4J_"; public static final String ICU4J = "ICU4J_";
/**
* This constant means that the BINARY columns are sorted as if the bytes were signed.
*/
public static final String SIGNED = "SIGNED";
/**
* This constant means that the BINARY columns are sorted as if the bytes were unsigned.
*/
public static final String UNSIGNED = "UNSIGNED";
private static CompareMode lastUsed; private static CompareMode lastUsed;
private static final boolean CAN_USE_ICU4J; private static final boolean CAN_USE_ICU4J;
...@@ -51,10 +61,13 @@ public class CompareMode { ...@@ -51,10 +61,13 @@ public class CompareMode {
private final String name; private final String name;
private final int strength; private final int strength;
/** if true, sort BINARY columns as if they contain unsigned bytes */
private final boolean binaryUnsigned;
protected CompareMode(String name, int strength) { protected CompareMode(String name, int strength, boolean binaryUnsigned) {
this.name = name; this.name = name;
this.strength = strength; this.strength = strength;
this.binaryUnsigned = binaryUnsigned;
} }
/** /**
...@@ -67,7 +80,7 @@ public class CompareMode { ...@@ -67,7 +80,7 @@ public class CompareMode {
* @param strength the collation strength * @param strength the collation strength
* @return the compare mode * @return the compare mode
*/ */
public static synchronized CompareMode getInstance(String name, int strength) { public static synchronized CompareMode getInstance(String name, int strength, boolean binaryUnsigned) {
if (lastUsed != null) { if (lastUsed != null) {
if (StringUtils.equals(lastUsed.name, name)) { if (StringUtils.equals(lastUsed.name, name)) {
if (lastUsed.strength == strength) { if (lastUsed.strength == strength) {
...@@ -76,7 +89,7 @@ public class CompareMode { ...@@ -76,7 +89,7 @@ public class CompareMode {
} }
} }
if (name == null || name.equals(OFF)) { if (name == null || name.equals(OFF)) {
lastUsed = new CompareMode(name, strength); lastUsed = new CompareMode(name, strength, binaryUnsigned);
} else { } else {
boolean useICU4J; boolean useICU4J;
if (name.startsWith(ICU4J)) { if (name.startsWith(ICU4J)) {
...@@ -89,9 +102,9 @@ public class CompareMode { ...@@ -89,9 +102,9 @@ public class CompareMode {
useICU4J = CAN_USE_ICU4J; useICU4J = CAN_USE_ICU4J;
} }
if (useICU4J) { if (useICU4J) {
lastUsed = new CompareModeIcu4J(name, strength); lastUsed = new CompareModeIcu4J(name, strength, binaryUnsigned);
} else { } else {
lastUsed = new CompareModeDefault(name, strength); lastUsed = new CompareModeDefault(name, strength, binaryUnsigned);
} }
} }
return lastUsed; return lastUsed;
...@@ -208,4 +221,8 @@ public class CompareMode { ...@@ -208,4 +221,8 @@ public class CompareMode {
return strength; return strength;
} }
public boolean isBinaryUnsigned() {
return binaryUnsigned;
}
} }
...@@ -20,8 +20,8 @@ public class CompareModeDefault extends CompareMode { ...@@ -20,8 +20,8 @@ public class CompareModeDefault extends CompareMode {
private final Collator collator; private final Collator collator;
private final SmallLRUCache<String, CollationKey> collationKeys; private final SmallLRUCache<String, CollationKey> collationKeys;
protected CompareModeDefault(String name, int strength) { protected CompareModeDefault(String name, int strength, boolean binaryUnsigned) {
super(name, strength); super(name, strength, binaryUnsigned);
collator = CompareMode.getCollator(name); collator = CompareMode.getCollator(name);
if (collator == null) { if (collator == null) {
throw DbException.throwInternalError(name); throw DbException.throwInternalError(name);
......
...@@ -20,8 +20,8 @@ public class CompareModeIcu4J extends CompareMode { ...@@ -20,8 +20,8 @@ public class CompareModeIcu4J extends CompareMode {
private final Comparator<String> collator; private final Comparator<String> collator;
protected CompareModeIcu4J(String name, int strength) { protected CompareModeIcu4J(String name, int strength, boolean binaryUnsigned) {
super(name, strength); super(name, strength, binaryUnsigned);
collator = getIcu4jCollator(name, strength); collator = getIcu4jCollator(name, strength);
} }
......
...@@ -87,8 +87,12 @@ public class ValueBytes extends Value { ...@@ -87,8 +87,12 @@ public class ValueBytes extends Value {
protected int compareSecure(Value v, CompareMode mode) { protected int compareSecure(Value v, CompareMode mode) {
byte[] v2 = ((ValueBytes) v).value; byte[] v2 = ((ValueBytes) v).value;
if (mode.isBinaryUnsigned()) {
return Utils.compareNotNullUnsigned(value, v2);
} else {
return Utils.compareNotNullSigned(value, v2); return Utils.compareNotNullSigned(value, v2);
} }
}
public String getString() { public String getString() {
return StringUtils.convertBytesToHex(value); return StringUtils.convertBytesToHex(value);
......
...@@ -96,6 +96,7 @@ public class TestCases extends TestBase { ...@@ -96,6 +96,7 @@ public class TestCases extends TestBase {
testDoubleRecovery(); testDoubleRecovery();
testConstraintReconnect(); testConstraintReconnect();
testCollation(); testCollation();
testBinaryCollation();
deleteDb("cases"); deleteDb("cases");
} }
...@@ -830,6 +831,43 @@ public class TestCases extends TestBase { ...@@ -830,6 +831,43 @@ public class TestCases extends TestBase {
conn.close(); conn.close();
} }
private void testBinaryCollation() throws SQLException {
deleteDb("cases");
Connection conn = getConnection("cases");
Statement stat = conn.createStatement();
ResultSet rs;
// test the default (SIGNED)
stat.execute("create table bin( x binary(1) );");
stat.execute("insert into bin(x) values (x'09'),(x'0a'),(x'99'),(x'aa');");
rs = stat.executeQuery("select * from bin order by x;");
rs.next();
assertEquals("99", rs.getString(1));
rs.next();
assertEquals("aa", rs.getString(1));
rs.next();
assertEquals("09", rs.getString(1));
rs.next();
assertEquals("0a", rs.getString(1));
// test UNSIGNED mode
stat.execute("drop table bin");
stat.execute("SET BINARY_COLLATION UNSIGNED");
stat.execute("create table bin( x binary(1) );");
stat.execute("insert into bin(x) values (x'09'),(x'0a'),(x'99'),(x'aa');");
rs = stat.executeQuery("select * from bin order by x;");
rs.next();
assertEquals("09", rs.getString(1));
rs.next();
assertEquals("0a", rs.getString(1));
rs.next();
assertEquals("99", rs.getString(1));
rs.next();
assertEquals("aa", rs.getString(1));
conn.close();
}
private void testPersistentSettings() throws SQLException { private void testPersistentSettings() throws SQLException {
deleteDb("cases"); deleteDb("cases");
Connection conn = getConnection("cases"); Connection conn = getConnection("cases");
......
...@@ -49,7 +49,7 @@ import org.h2.value.ValueUuid; ...@@ -49,7 +49,7 @@ import org.h2.value.ValueUuid;
public class TestDataPage extends TestBase implements DataHandler { public class TestDataPage extends TestBase implements DataHandler {
private boolean testPerformance; private boolean testPerformance;
private final CompareMode compareMode = CompareMode.getInstance(null, 0); private final CompareMode compareMode = CompareMode.getInstance(null, 0, false);
/** /**
* Run just this test. * Run just this test.
......
...@@ -32,18 +32,18 @@ public class TestPattern extends TestBase { ...@@ -32,18 +32,18 @@ public class TestPattern extends TestBase {
private void testCompareModeReuse() { private void testCompareModeReuse() {
CompareMode mode1, mode2; CompareMode mode1, mode2;
mode1 = CompareMode.getInstance(null, 0); mode1 = CompareMode.getInstance(null, 0, false);
mode2 = CompareMode.getInstance(null, 0); mode2 = CompareMode.getInstance(null, 0, false);
assertTrue(mode1 == mode2); assertTrue(mode1 == mode2);
mode1 = CompareMode.getInstance("DE", Collator.SECONDARY); mode1 = CompareMode.getInstance("DE", Collator.SECONDARY, false);
assertFalse(mode1 == mode2); assertFalse(mode1 == mode2);
mode2 = CompareMode.getInstance("DE", Collator.SECONDARY); mode2 = CompareMode.getInstance("DE", Collator.SECONDARY, false);
assertTrue(mode1 == mode2); assertTrue(mode1 == mode2);
} }
private void testPattern() { private void testPattern() {
CompareMode mode = CompareMode.getInstance(null, 0); CompareMode mode = CompareMode.getInstance(null, 0, false);
CompareLike comp = new CompareLike(mode, "\\", null, null, null, false); CompareLike comp = new CompareLike(mode, "\\", null, null, null, false);
test(comp, "B", "%_"); test(comp, "B", "%_");
test(comp, "A", "A%"); test(comp, "A", "A%");
......
...@@ -30,7 +30,7 @@ import org.h2.value.ValueInt; ...@@ -30,7 +30,7 @@ import org.h2.value.ValueInt;
*/ */
public class TestValueHashMap extends TestBase implements DataHandler { public class TestValueHashMap extends TestBase implements DataHandler {
CompareMode compareMode = CompareMode.getInstance(null, 0); CompareMode compareMode = CompareMode.getInstance(null, 0, false);
/** /**
* Run just this test. * Run just this test.
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论