提交 d217b4aa authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #533 from httpdigest/master

Implement right-padding of CHAR(N) datatype in PostgreSQL mode
...@@ -21,7 +21,9 @@ Change Log ...@@ -21,7 +21,9 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <ul>
<li>- <li>Add padding for CHAR(N) values in PostgreSQL mode
</li>
<li>
</li> </li>
</ul> </ul>
......
...@@ -140,7 +140,7 @@ public class Insert extends Prepared implements ResultTarget { ...@@ -140,7 +140,7 @@ public class Insert extends Prepared implements ResultTarget {
// e can be null (DEFAULT) // e can be null (DEFAULT)
e = e.optimize(session); e = e.optimize(session);
try { try {
Value v = c.convert(e.getValue(session)); Value v = c.convert(e.getValue(session), session.getDatabase().getMode());
newRow.setValue(index, v); newRow.setValue(index, v);
} catch (DbException ex) { } catch (DbException ex) {
throw setRow(ex, x, getSQL(expr)); throw setRow(ex, x, getSQL(expr));
...@@ -186,7 +186,7 @@ public class Insert extends Prepared implements ResultTarget { ...@@ -186,7 +186,7 @@ public class Insert extends Prepared implements ResultTarget {
Column c = columns[j]; Column c = columns[j];
int index = c.getColumnId(); int index = c.getColumnId();
try { try {
Value v = c.convert(values[j]); Value v = c.convert(values[j], session.getDatabase().getMode());
newRow.setValue(index, v); newRow.setValue(index, v);
} catch (DbException ex) { } catch (DbException ex) {
throw setRow(ex, rowNumber, getSQL(values)); throw setRow(ex, rowNumber, getSQL(values));
......
...@@ -160,6 +160,11 @@ public class Mode { ...@@ -160,6 +160,11 @@ public class Mode {
*/ */
public boolean allowAffinityKey; public boolean allowAffinityKey;
/**
* Whether to right-pad fixed strings with spaces.
*/
public boolean padFixedLengthStrings;
private final String name; private final String name;
static { static {
...@@ -256,6 +261,7 @@ public class Mode { ...@@ -256,6 +261,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx = mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName"); Pattern.compile("ApplicationName");
mode.prohibitEmptyInPredicate = true; mode.prohibitEmptyInPredicate = true;
mode.padFixedLengthStrings = true;
add(mode); add(mode);
mode = new Mode("Ignite"); mode = new Mode("Ignite");
......
...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition; ...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
...@@ -201,7 +202,7 @@ public class Comparison extends Condition { ...@@ -201,7 +202,7 @@ public class Comparison extends Condition {
// to constant type, but vise versa, then let's do this here // to constant type, but vise versa, then let's do this here
// once. // once.
if (constType != resType) { if (constType != resType) {
right = ValueExpression.get(r.convertTo(resType)); right = ValueExpression.get(r.convertTo(resType, MathUtils.convertLongToInt(left.getPrecision()), session.getDatabase().getMode()));
} }
} else if (right instanceof Parameter) { } else if (right instanceof Parameter) {
((Parameter) right).setColumn( ((Parameter) right).setColumn(
......
...@@ -163,8 +163,21 @@ public class Column { ...@@ -163,8 +163,21 @@ public class Column {
* @return the value * @return the value
*/ */
public Value convert(Value v) { public Value convert(Value v) {
return convert(v, null);
}
/**
* Convert a value to this column's type using the given {@link Mode}.
* <p>
* Use this method in case the conversion is Mode-dependent.
*
* @param v the value
* @param mode the database {@link Mode} to use
* @return the value
*/
public Value convert(Value v, Mode mode) {
try { try {
return v.convertTo(type); return v.convertTo(type, MathUtils.convertLongToInt(precision), mode);
} catch (DbException e) { } catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) {
String target = (table == null ? "" : table.getName() + ": ") + String target = (table == null ? "" : table.getName() + ": ") +
......
...@@ -615,7 +615,7 @@ public class Transfer { ...@@ -615,7 +615,7 @@ public class Transfer {
case Value.STRING_IGNORECASE: case Value.STRING_IGNORECASE:
return ValueStringIgnoreCase.get(readString()); return ValueStringIgnoreCase.get(readString());
case Value.STRING_FIXED: case Value.STRING_FIXED:
return ValueStringFixed.get(readString()); return ValueStringFixed.get(readString(), ValueStringFixed.PRECISION_DO_NOT_TRIM, null);
case Value.BLOB: { case Value.BLOB: {
long length = readLong(); long length = readLong();
if (version >= Constants.TCP_PROTOCOL_VERSION_11) { if (version >= Constants.TCP_PROTOCOL_VERSION_11) {
......
...@@ -20,6 +20,7 @@ import java.sql.Timestamp; ...@@ -20,6 +20,7 @@ import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
...@@ -541,6 +542,21 @@ public abstract class Value { ...@@ -541,6 +542,21 @@ public abstract class Value {
* @return the converted value * @return the converted value
*/ */
public Value convertTo(int targetType) { public Value convertTo(int targetType) {
// Use -1 to indicate "default behaviour" where value conversion should not
// depend on any datatype precision.
return convertTo(targetType, -1, null);
}
/**
* Compare a value to the specified type.
*
* @param targetType the type of the returned value
* @param the precision of the column to convert this value to.
* The special constant <code>-1</code> is used to indicate that
* the precision plays no role when converting the value
* @return the converted value
*/
public Value convertTo(int targetType, int precision, Mode mode) {
// converting NULL is done in ValueNull // converting NULL is done in ValueNull
// converting BLOB to CLOB and vice versa is done in ValueLob // converting BLOB to CLOB and vice versa is done in ValueLob
if (getType() == targetType) { if (getType() == targetType) {
...@@ -962,7 +978,7 @@ public abstract class Value { ...@@ -962,7 +978,7 @@ public abstract class Value {
case STRING_IGNORECASE: case STRING_IGNORECASE:
return ValueStringIgnoreCase.get(s); return ValueStringIgnoreCase.get(s);
case STRING_FIXED: case STRING_FIXED:
return ValueStringFixed.get(s); return ValueStringFixed.get(s, precision, mode);
case DOUBLE: case DOUBLE:
return ValueDouble.get(Double.parseDouble(s.trim())); return ValueDouble.get(Double.parseDouble(s.trim()));
case FLOAT: case FLOAT:
......
...@@ -12,6 +12,8 @@ import java.util.Arrays; ...@@ -12,6 +12,8 @@ import java.util.Arrays;
import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter; import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.geom.PrecisionModel;
import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Envelope;
...@@ -272,11 +274,11 @@ public class ValueGeometry extends Value { ...@@ -272,11 +274,11 @@ public class ValueGeometry extends Value {
} }
@Override @Override
public Value convertTo(int targetType) { public Value convertTo(int targetType, int precision, Mode mode) {
if (targetType == Value.JAVA_OBJECT) { if (targetType == Value.JAVA_OBJECT) {
return this; return this;
} }
return super.convertTo(targetType); return super.convertTo(targetType, precision, mode);
} }
/** /**
......
...@@ -14,6 +14,7 @@ import java.io.Reader; ...@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
...@@ -443,7 +444,7 @@ public class ValueLob extends Value { ...@@ -443,7 +444,7 @@ public class ValueLob extends Value {
* @return the converted value * @return the converted value
*/ */
@Override @Override
public Value convertTo(int t) { public Value convertTo(int t, int precision, Mode mode) {
if (t == type) { if (t == type) {
return this; return this;
} else if (t == Value.CLOB) { } else if (t == Value.CLOB) {
...@@ -453,7 +454,7 @@ public class ValueLob extends Value { ...@@ -453,7 +454,7 @@ public class ValueLob extends Value {
ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler); ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler);
return copy; return copy;
} }
return super.convertTo(t); return super.convertTo(t, precision, mode);
} }
@Override @Override
......
...@@ -14,6 +14,7 @@ import java.io.Reader; ...@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
...@@ -184,7 +185,7 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -184,7 +185,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
* @return the converted value * @return the converted value
*/ */
@Override @Override
public Value convertTo(int t) { public Value convertTo(int t, int precision, Mode mode) {
if (t == type) { if (t == type) {
return this; return this;
} else if (t == Value.CLOB) { } else if (t == Value.CLOB) {
...@@ -204,7 +205,7 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -204,7 +205,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
return ValueLobDb.createSmallLob(t, small); return ValueLobDb.createSmallLob(t, small);
} }
} }
return super.convertTo(t); return super.convertTo(t, precision, mode);
} }
@Override @Override
......
...@@ -14,6 +14,7 @@ import java.sql.SQLException; ...@@ -14,6 +14,7 @@ import java.sql.SQLException;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
/** /**
...@@ -132,7 +133,7 @@ public class ValueNull extends Value { ...@@ -132,7 +133,7 @@ public class ValueNull extends Value {
} }
@Override @Override
public Value convertTo(int type) { public Value convertTo(int type, int precision, Mode mode) {
return this; return this;
} }
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
*/ */
package org.h2.value; package org.h2.value;
import java.util.Arrays;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
...@@ -13,6 +16,18 @@ import org.h2.util.StringUtils; ...@@ -13,6 +16,18 @@ import org.h2.util.StringUtils;
*/ */
public class ValueStringFixed extends ValueString { public class ValueStringFixed extends ValueString {
/**
* Special value for the precision in {@link #get(String, int, Mode)} to indicate that the value
* should <i>not</i> be trimmed.
*/
public static final int PRECISION_DO_NOT_TRIM = Integer.MIN_VALUE;
/**
* Special value for the precision in {@link #get(String, int, Mode)} to indicate that the default
* behaviour should of trimming the value should apply.
*/
public static final int PRECISION_TRIM = -1;
private static final ValueStringFixed EMPTY = new ValueStringFixed(""); private static final ValueStringFixed EMPTY = new ValueStringFixed("");
protected ValueStringFixed(String value) { protected ValueStringFixed(String value) {
...@@ -20,15 +35,29 @@ public class ValueStringFixed extends ValueString { ...@@ -20,15 +35,29 @@ public class ValueStringFixed extends ValueString {
} }
private static String trimRight(String s) { private static String trimRight(String s) {
return trimRight(s, 0);
}
private static String trimRight(String s, int minLength) {
int endIndex = s.length() - 1; int endIndex = s.length() - 1;
int i = endIndex; int i = endIndex;
while (i >= 0 && s.charAt(i) == ' ') { while (i >= minLength && s.charAt(i) == ' ') {
i--; i--;
} }
s = i == endIndex ? s : s.substring(0, i + 1); s = i == endIndex ? s : s.substring(0, i + 1);
return s; return s;
} }
private static String rightPadWithSpaces(String s, int length) {
int pad = length - s.length();
if (pad <= 0) {
return s;
}
char[] res = new char[length];
s.getChars(0, s.length(), res, 0);
Arrays.fill(res, s.length(), length, ' ');
return new String(res);
}
@Override @Override
public int getType() { public int getType() {
return Value.STRING_FIXED; return Value.STRING_FIXED;
...@@ -42,7 +71,42 @@ public class ValueStringFixed extends ValueString { ...@@ -42,7 +71,42 @@ public class ValueStringFixed extends ValueString {
* @return the value * @return the value
*/ */
public static ValueStringFixed get(String s) { public static ValueStringFixed get(String s) {
// Use the special precision constant PRECISION_TRIM to indicate
// default H2 behaviour of trimming the value.
return get(s, PRECISION_TRIM, null);
}
/**
* Get or create a fixed length string value for the given string.
* <p>
* This method will use a {@link Mode}-specific conversion when <code>mode</code> is not <code>null</code>.
* Otherwise it will use the default H2 behaviour of trimming the given string if <code>precision</code>
* is not {@link #PRECISION_DO_NOT_TRIM}.
*
* @param s the string
* @param precision if the {@link Mode#padFixedLengthStrings} indicates that strings should be padded, this
* defines the overall length of the (potentially padded) string.
* If the special constant {@link #PRECISION_DO_NOT_TRIM} is used the value will not be trimmed.
* @return the value
*/
public static ValueStringFixed get(String s, int precision, Mode mode) {
// Should fixed strings be padded?
if (mode != null && mode.padFixedLengthStrings) {
if (precision == Integer.MAX_VALUE) {
// CHAR without a length specification is identical to CHAR(1)
precision = 1;
}
if (s.length() < precision) {
// We have to pad
s = rightPadWithSpaces(s, precision);
} else {
// We should trim, because inserting 'A ' into a CHAR(1) is possible!
s = trimRight(s, precision);
}
} else if (precision != PRECISION_DO_NOT_TRIM) {
// Default behaviour of H2
s = trimRight(s); s = trimRight(s);
}
if (s.length() == 0) { if (s.length() == 0) {
return EMPTY; return EMPTY;
} }
......
...@@ -245,6 +245,34 @@ public class TestCompatibility extends TestBase { ...@@ -245,6 +245,34 @@ public class TestCompatibility extends TestBase {
assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)"); assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)");
assertResult("ABCD", stat, "SELECT SUBSTRING('0ABCDEF' FROM 2 FOR 4)"); assertResult("ABCD", stat, "SELECT SUBSTRING('0ABCDEF' FROM 2 FOR 4)");
/* Test right-padding of CHAR(N) at INSERT */
stat.execute("CREATE TABLE TEST(CH CHAR(10))");
stat.execute("INSERT INTO TEST (CH) VALUES ('Hello')");
assertResult("Hello ", stat, "SELECT CH FROM TEST");
/* Test that WHERE clauses accept unpadded values and will pad before comparison */
assertResult("Hello ", stat, "SELECT CH FROM TEST WHERE CH = 'Hello'");
/* Test CHAR which is identical to CHAR(1) */
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("CREATE TABLE TEST(CH CHAR)");
stat.execute("INSERT INTO TEST (CH) VALUES ('')");
assertResult(" ", stat, "SELECT CH FROM TEST");
assertResult(" ", stat, "SELECT CH FROM TEST WHERE CH = ''");
/* Test that excessive spaces are trimmed */
stat.execute("DELETE FROM TEST");
stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')");
assertResult("1", stat, "SELECT CH FROM TEST");
assertResult("1", stat, "SELECT CH FROM TEST WHERE CH = '1 '");
/* Test that we do not trim too far */
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("CREATE TABLE TEST(CH CHAR(2))");
stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')");
assertResult("1 ", stat, "SELECT CH FROM TEST");
assertResult("1 ", stat, "SELECT CH FROM TEST WHERE CH = '1 '");
} }
private void testMySQL() throws SQLException { private void testMySQL() throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论