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

Issue 520: Add support for sequence max value, min value and cycle, patch by Daniel Gredler.

上级 c7687784
......@@ -208,8 +208,12 @@ ALTER SCHEMA TEST RENAME TO PRODUCTION
"Commands (DDL)","ALTER SEQUENCE","
ALTER SEQUENCE sequenceName [ RESTART WITH long ] [ INCREMENT BY long ]
[ MINVALUE long | NOMINVALUE | NO MINVALUE ]
[ MAXVALUE long | NOMAXVALUE | NO MAXVALUE ]
[ CYCLE long | NOCYCLE | NO CYCLE ]
[ CACHE long | NOCACHE | NO CACHE ]
","
Changes the next value and the increment of a sequence.
Changes the parameters of a sequence.
This command does not commit the current transaction; however the new value is used by other
transactions immediately, and rolling back this command has no effect.
","
......@@ -566,7 +570,11 @@ CREATE SCHEMA TEST_SCHEMA AUTHORIZATION SA
"Commands (DDL)","CREATE SEQUENCE","
CREATE SEQUENCE [ IF NOT EXISTS ] newSequenceName [ START WITH long ]
[ INCREMENT BY long ] [ CACHE long ]
[ INCREMENT BY long ]
[ MINVALUE long | NOMINVALUE | NO MINVALUE ]
[ MAXVALUE long | NOMAXVALUE | NO MAXVALUE ]
[ CYCLE long | NOCYCLE | NO CYCLE ]
[ CACHE long | NOCACHE | NO CACHE ]
","
Creates a new sequence.
The data type of a sequence is BIGINT.
......
......@@ -28,6 +28,7 @@ Change Log
</li><li>Issue 518: java.sql.Connection.commit() freezes after LOB modification with EXCLUSIVE connection
</li><li>Issue 517: Create or replace view statement has no effect on the others already existing JDBC connection
</li><li>Support 123L syntax as in Java; example: SELECT (2000000000L*2).
</li><li>Issue 520: Add support for sequence max value, min value and cycle, patch by Daniel Gredler.
</li></ul>
<h2>Version 1.3.174 (2013-10-19)</h2>
......
......@@ -189,7 +189,7 @@ public class Parser {
private String sqlCommand;
/** cached array if chars from sqlCommand */
private char[] sqlCommandChars;
/** index into sqlCommand of previous token */
/** index into sqlCommand of previous token */
private int lastParseIndex;
/** index into sqlCommand of current token */
private int parseIndex;
......@@ -4183,8 +4183,34 @@ public class Parser {
} else if (readIf("INCREMENT")) {
readIf("BY");
command.setIncrement(readExpression());
} else if (readIf("MINVALUE")) {
command.setMinValue(readExpression());
} else if (readIf("NOMINVALUE")) {
command.setMinValue(null);
} else if (readIf("MAXVALUE")) {
command.setMaxValue(readExpression());
} else if (readIf("NOMAXVALUE")) {
command.setMaxValue(null);
} else if (readIf("CYCLE")) {
command.setCycle(true);
} else if (readIf("NOCYCLE")) {
command.setCycle(false);
} else if (readIf("NO")) {
if (readIf("MINVALUE")) {
command.setMinValue(null);
} else if (readIf("MAXVALUE")) {
command.setMaxValue(null);
} else if (readIf("CYCLE")) {
command.setCycle(false);
} else if (readIf("CACHE")) {
command.setCacheSize(ValueExpression.get(ValueLong.get(1)));
} else {
break;
}
} else if (readIf("CACHE")) {
command.setCacheSize(readExpression());
} else if (readIf("NOCACHE")) {
command.setCacheSize(ValueExpression.get(ValueLong.get(1)));
} else if (readIf("BELONGS_TO_TABLE")) {
command.setBelongsToTable(true);
} else {
......@@ -4524,13 +4550,44 @@ public class Parser {
Sequence sequence = getSchema().getSequence(sequenceName);
AlterSequence command = new AlterSequence(session, sequence.getSchema());
command.setSequence(sequence);
if (readIf("RESTART")) {
read("WITH");
command.setStartWith(readExpression());
}
if (readIf("INCREMENT")) {
read("BY");
command.setIncrement(readExpression());
while (true) {
if (readIf("RESTART")) {
read("WITH");
command.setStartWith(readExpression());
} else if (readIf("INCREMENT")) {
read("BY");
command.setIncrement(readExpression());
} else if (readIf("MINVALUE")) {
command.setMinValue(readExpression());
} else if (readIf("NOMINVALUE")) {
command.setMinValue(null);
} else if (readIf("MAXVALUE")) {
command.setMaxValue(readExpression());
} else if (readIf("NOMAXVALUE")) {
command.setMaxValue(null);
} else if (readIf("CYCLE")) {
command.setCycle(true);
} else if (readIf("NOCYCLE")) {
command.setCycle(false);
} else if (readIf("NO")) {
if (readIf("MINVALUE")) {
command.setMinValue(null);
} else if (readIf("MAXVALUE")) {
command.setMaxValue(null);
} else if (readIf("CYCLE")) {
command.setCycle(false);
} else if (readIf("CACHE")) {
command.setCacheSize(ValueExpression.get(ValueLong.get(1)));
} else {
break;
}
} else if (readIf("CACHE")) {
command.setCacheSize(readExpression());
} else if (readIf("NOCACHE")) {
command.setCacheSize(ValueExpression.get(ValueLong.get(1)));
} else {
break;
}
}
return command;
}
......
......@@ -23,6 +23,9 @@ public class CreateSequence extends SchemaCommand {
private String sequenceName;
private boolean ifNotExists;
private boolean cycle;
private Expression minValue;
private Expression maxValue;
private Expression start;
private Expression increment;
private Expression cacheSize;
......@@ -40,6 +43,10 @@ public class CreateSequence extends SchemaCommand {
this.ifNotExists = ifNotExists;
}
public void setCycle(boolean cycle) {
this.cycle = cycle;
}
@Override
public int update() {
session.commit(true);
......@@ -51,17 +58,20 @@ public class CreateSequence extends SchemaCommand {
throw DbException.get(ErrorCode.SEQUENCE_ALREADY_EXISTS_1, sequenceName);
}
int id = getObjectId();
Sequence sequence = new Sequence(getSchema(), id, sequenceName, belongsToTable);
sequence.setStartValue(getLong(start, 1));
sequence.setIncrement(getLong(increment, 1));
sequence.setCacheSize(getLong(cacheSize, Sequence.DEFAULT_CACHE_SIZE));
Long startValue = getLong(start);
Long inc = getLong(increment);
Long cache = getLong(cacheSize);
Long min = getLong(minValue);
Long max = getLong(maxValue);
Sequence sequence = new Sequence(getSchema(), id, sequenceName, startValue, inc,
cache, min, max, cycle, belongsToTable);
db.addSchemaObject(session, sequence);
return 0;
}
private long getLong(Expression expr, long defaultValue) {
private Long getLong(Expression expr) {
if (expr == null) {
return defaultValue;
return null;
}
return expr.optimize(session).getValue(session).getLong();
}
......@@ -74,6 +84,14 @@ public class CreateSequence extends SchemaCommand {
this.increment = increment;
}
public void setMinValue(Expression minValue) {
this.minValue = minValue;
}
public void setMaxValue(Expression maxValue) {
this.maxValue = maxValue;
}
public void setBelongsToTable(boolean belongsToTable) {
this.belongsToTable = belongsToTable;
}
......
......@@ -29,6 +29,10 @@ public class AlterSequence extends SchemaCommand {
private Sequence sequence;
private Expression start;
private Expression increment;
private Boolean cycle;
private Expression minValue;
private Expression maxValue;
private Expression cacheSize;
public AlterSequence(Session session, Schema schema) {
super(session, schema);
......@@ -59,22 +63,41 @@ public class AlterSequence extends SchemaCommand {
this.increment = increment;
}
public void setCycle(Boolean cycle) {
this.cycle = cycle;
}
public void setMinValue(Expression minValue) {
this.minValue = minValue;
}
public void setMaxValue(Expression maxValue) {
this.maxValue = maxValue;
}
public void setCacheSize(Expression cacheSize) {
this.cacheSize = cacheSize;
}
@Override
public int update() {
Database db = session.getDatabase();
if (table != null) {
session.getUser().checkRight(table, Right.ALL);
}
if (start != null) {
long startValue = start.optimize(session).getValue(session).getLong();
sequence.setStartValue(startValue);
if (cycle != null) {
sequence.setCycle(cycle);
}
if (increment != null) {
long incrementValue = increment.optimize(session).getValue(session).getLong();
if (incrementValue == 0) {
throw DbException.getInvalidValueException("INCREMENT", 0);
}
sequence.setIncrement(incrementValue);
if (cacheSize != null) {
long size = cacheSize.optimize(session).getValue(session).getLong();
sequence.setCacheSize(size);
}
if (start != null || minValue != null || maxValue != null || increment != null) {
Long startValue = getLong(start);
Long min = getLong(minValue);
Long max = getLong(maxValue);
Long inc = getLong(increment);
sequence.modify(startValue, min, max, inc);
}
// need to use the system session, so that the update
// can be committed immediately - not committing it
......@@ -87,6 +110,13 @@ public class AlterSequence extends SchemaCommand {
return 0;
}
private Long getLong(Expression expr) {
if (expr == null) {
return null;
}
return expr.optimize(session).getValue(session).getLong();
}
@Override
public int getType() {
return CommandInterface.ALTER_SEQUENCE;
......
......@@ -458,6 +458,13 @@ public class ErrorCode {
*/
public static final int TRIGGER_SELECT_AND_ROW_BASED_NOT_SUPPORTED = 90005;
/**
* The error with code <code>90006</code> is thrown when
* trying to get a value from a sequence that has run out of numbers
* and does not have cycling enabled.
*/
public static final int SEQUENCE_EXHAUSTED = 90006;
/**
* The error with code <code>90007</code> is thrown when
* trying to call a JDBC method on an object that has been closed.
......@@ -474,6 +481,13 @@ public class ErrorCode {
*/
public static final int INVALID_VALUE_2 = 90008;
/**
* The error with code <code>90009</code> is thrown when
* trying to create a sequence with an invalid combination
* of attributes (min value, max value, start value, etc).
*/
public static final int SEQUENCE_ATTRIBUTES_INVALID = 90009;
/**
* The error with code <code>22007</code> is thrown when
* a text can not be converted to a date, time, or timestamp constant.
......@@ -1870,7 +1884,7 @@ public class ErrorCode {
public static final int JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE = 90141;
// next are 90006, 90009, 90010, 90011, 90021, 90039,
// next are 90010, 90011, 90021, 90039,
// 90051, 90056, 90110, 90122, 90142
private ErrorCode() {
......
......@@ -33,8 +33,10 @@
90002=Method is only allowed for a query. Use execute or executeUpdate instead of executeQuery
90003=Hexadecimal string with odd number of characters: {0}
90004=Hexadecimal string contains non-hex character: {0}
90006=Sequence {0} has run out of numbers
90007=The object is already closed
90008=Invalid value {0} for parameter {1}
90009=Unable to create or alter sequence {0} because of invalid attributes (start value {1}, min value {2}, max value {3}, increment {4})
90012=Parameter {0} is not set
90013=Database {0} not found
90014=Error parsing {0}
......
......@@ -6,6 +6,8 @@
*/
package org.h2.schema;
import java.math.BigInteger;
import org.h2.constant.ErrorCode;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
import org.h2.message.DbException;
......@@ -13,7 +15,7 @@ import org.h2.message.Trace;
import org.h2.table.Table;
/**
*A sequence is created using the statement
* A sequence is created using the statement
* CREATE SEQUENCE
*/
public class Sequence extends SchemaObjectBase {
......@@ -23,20 +25,109 @@ public class Sequence extends SchemaObjectBase {
*/
public static final int DEFAULT_CACHE_SIZE = 32;
private long value = 1;
private long value;
private long valueWithMargin;
private long increment = 1;
private long cacheSize = DEFAULT_CACHE_SIZE;
private long increment;
private long cacheSize;
private long minValue;
private long maxValue;
private boolean cycle;
private boolean belongsToTable;
public Sequence(Schema schema, int id, String name, boolean belongsToTable) {
/**
* Creates a new sequence for an auto-increment column.
*/
public Sequence(Schema schema, int id, String name, long startValue, long increment) {
this(schema, id, name, startValue, increment, null, null, null, false, true);
}
/**
* Creates a new sequence.
*/
public Sequence(Schema schema, int id, String name, Long startValue, Long increment, Long cacheSize,
Long minValue, Long maxValue, boolean cycle, boolean belongsToTable) {
initSchemaObjectBase(schema, id, name, Trace.SEQUENCE);
this.increment = increment != null ? increment : 1;
this.minValue = minValue != null ? minValue : getDefaultMinValue(this.increment);
this.maxValue = maxValue != null ? maxValue : getDefaultMaxValue(this.increment);
this.value = startValue != null ? startValue : getDefaultStartValue(this.increment);
this.valueWithMargin = value;
this.cacheSize = cacheSize != null ? Math.max(1, cacheSize) : DEFAULT_CACHE_SIZE;
this.cycle = cycle;
this.belongsToTable = belongsToTable;
if (!isValid(this.value, this.minValue, this.maxValue, this.increment)) {
throw DbException.get(ErrorCode.SEQUENCE_ATTRIBUTES_INVALID, name, String.valueOf(this.value),
String.valueOf(this.minValue), String.valueOf(this.maxValue), String.valueOf(this.increment));
}
}
public synchronized void setStartValue(long value) {
this.value = value;
this.valueWithMargin = value;
/**
* Allows the start value, increment, min value and max value to be updated
* atomically, including atomic validation. Useful because setting these
* attributes one after the other could otherwise result in an invalid
* sequence state (e.g. min value > max value, start value < min value,
* etc).
*
* @param startValue the new start value (<code>null</code> if no change)
* @param minValue the new min value (<code>null</code> if no change)
* @param maxValue the new max value (<code>null</code> if no change)
* @param increment the new increment (<code>null</code> if no change)
*/
public synchronized void modify(Long startValue, Long minValue, Long maxValue, Long increment) {
if (startValue == null) {
startValue = this.value;
}
if (minValue == null) {
minValue = this.minValue;
}
if (maxValue == null) {
maxValue = this.maxValue;
}
if (increment == null) {
increment = this.increment;
}
if (!isValid(startValue, minValue, maxValue, increment)) {
throw DbException.get(ErrorCode.SEQUENCE_ATTRIBUTES_INVALID, getName(), String.valueOf(this.value),
String.valueOf(this.minValue), String.valueOf(this.maxValue), String.valueOf(this.increment));
}
this.value = startValue;
this.valueWithMargin = startValue;
this.minValue = minValue;
this.maxValue = maxValue;
this.increment = increment;
}
/**
* Validates the specified prospective start value, min value, max value and increment relative
* to each other, since each of their respective validities are contingent on the values of the
* other parameters.
*
* @param value the prospective start value
* @param minValue the prospective min value
* @param maxValue the prospective max value
* @param increment the prospective increment
*/
private static boolean isValid(long value, long minValue, long maxValue, long increment) {
return minValue <= value &&
maxValue >= value &&
maxValue > minValue &&
increment != 0 &&
// Math.abs(increment) < maxValue - minValue
// use BigInteger to avoid overflows when maxValue and minValue are really big
BigInteger.valueOf(increment).abs().compareTo(
BigInteger.valueOf(maxValue).subtract(BigInteger.valueOf(minValue))) < 0;
}
private long getDefaultMinValue(long increment) {
return increment >= 0 ? 1 : Long.MIN_VALUE;
}
private long getDefaultMaxValue(long increment) {
return increment >= 0 ? Long.MAX_VALUE : -1;
}
private long getDefaultStartValue(long increment) {
return increment >= 0 ? minValue : maxValue;
}
public boolean getBelongsToTable() {
......@@ -47,11 +138,20 @@ public class Sequence extends SchemaObjectBase {
return increment;
}
public void setIncrement(long inc) {
if (inc == 0) {
throw DbException.getInvalidValueException("INCREMENT", 0);
}
this.increment = inc;
public long getMinValue() {
return minValue;
}
public long getMaxValue() {
return maxValue;
}
public boolean getCycle() {
return cycle;
}
public void setCycle(boolean cycle) {
this.cycle = cycle;
}
@Override
......@@ -74,6 +174,15 @@ public class Sequence extends SchemaObjectBase {
if (increment != 1) {
buff.append(" INCREMENT BY ").append(increment);
}
if (minValue != getDefaultMinValue(increment)) {
buff.append(" MINVALUE ").append(minValue);
}
if (maxValue != getDefaultMaxValue(increment)) {
buff.append(" MAXVALUE ").append(maxValue);
}
if (cycle) {
buff.append(" CYCLE");
}
if (cacheSize != DEFAULT_CACHE_SIZE) {
buff.append(" CACHE ").append(cacheSize);
}
......@@ -90,8 +199,21 @@ public class Sequence extends SchemaObjectBase {
* @return the next value
*/
public synchronized long getNext(Session session) {
boolean needsFlush = false;
if ((increment > 0 && value >= valueWithMargin) || (increment < 0 && value <= valueWithMargin)) {
valueWithMargin += increment * cacheSize;
needsFlush = true;
}
if ((increment > 0 && value > maxValue) || (increment < 0 && value < minValue)) {
if (cycle) {
value = increment > 0 ? minValue : maxValue;
valueWithMargin = value + (increment * cacheSize);
needsFlush = true;
} else {
throw DbException.get(ErrorCode.SEQUENCE_EXHAUSTED, getName());
}
}
if (needsFlush) {
flush(session);
}
long v = value;
......
......@@ -334,7 +334,7 @@ public class Column {
update = true;
}
if (update) {
sequence.setStartValue(now + inc);
sequence.modify(now + inc, null, null, null);
session.setLastIdentity(ValueLong.get(now));
sequence.flush(session);
}
......@@ -370,9 +370,7 @@ public class Column {
break;
}
}
Sequence seq = new Sequence(schema, id, sequenceName, true);
seq.setStartValue(start);
seq.setIncrement(increment);
Sequence seq = new Sequence(schema, id, sequenceName, start, increment);
if (!temporary) {
session.getDatabase().addSchemaObject(session, seq);
}
......
......@@ -252,6 +252,9 @@ public class MetaTable extends Table {
"IS_GENERATED BIT",
"REMARKS",
"CACHE BIGINT",
"MIN_VALUE BIGINT",
"MAX_VALUE BIGINT",
"IS_CYCLE BIT",
"ID INT"
);
break;
......@@ -1007,10 +1010,16 @@ public class MetaTable extends Table {
String.valueOf(s.getIncrement()),
// IS_GENERATED
s.getBelongsToTable() ? "TRUE" : "FALSE",
// REMARKS
// REMARKS
replaceNullWithEmpty(s.getComment()),
// CACHE
String.valueOf(s.getCacheSize()),
// MIN_VALUE
String.valueOf(s.getMinValue()),
// MAX_VALUE
String.valueOf(s.getMaxValue()),
// IS_CYCLE
s.getCycle() ? "TRUE" : "FALSE",
// ID
"" + s.getId()
);
......
......@@ -8319,7 +8319,7 @@ DROP SEQUENCE IF EXISTS TEST_SEQ;
DROP SEQUENCE IF EXISTS TEST_SEQ;
> ok
CREATE SEQUENCE TEST_LONG START WITH 90123456789012345 INCREMENT BY -1;
CREATE SEQUENCE TEST_LONG START WITH 90123456789012345 MAXVALUE 90123456789012345 INCREMENT BY -1;
> ok
SET AUTOCOMMIT FALSE;
......
......@@ -740,4 +740,6 @@ persists forwarding periods discussion whatever revisit decision
detrimental dedicated kaiser perhaps chromium shortened
layers waited descent spliced abstracts planning interest among sliced
lives pauses allocates kicks introduction straightforward getenv
ordinate tweaking fetching rfe yates cookie btrfs cookies
\ No newline at end of file
ordinate tweaking fetching rfe yates cookie btrfs cookies
nocycle nomaxvalue nominvalue cycling proceed prospective exhausted contingent
validities hang degenerates freezes
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论