org.glowroot.shaded.h2.schema.Sequence Maven / Gradle / Ivy
The newest version!
/*
* 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.glowroot.shaded.h2.schema;
import java.math.BigInteger;
import org.glowroot.shaded.h2.api.ErrorCode;
import org.glowroot.shaded.h2.engine.DbObject;
import org.glowroot.shaded.h2.engine.Session;
import org.glowroot.shaded.h2.message.DbException;
import org.glowroot.shaded.h2.message.Trace;
import org.glowroot.shaded.h2.table.Table;
/**
* A sequence is created using the statement
* CREATE SEQUENCE
*/
public class Sequence extends SchemaObjectBase {
/**
* The default cache size for sequences.
*/
public static final int DEFAULT_CACHE_SIZE = 32;
private long value;
private long valueWithMargin;
private long increment;
private long cacheSize;
private long minValue;
private long maxValue;
private boolean cycle;
private boolean belongsToTable;
/**
* Creates a new sequence for an auto-increment column.
*
* @param schema the schema
* @param id the object id
* @param name the sequence name
* @param startValue the first value to return
* @param increment the increment count
*/
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.
*
* @param schema the schema
* @param id the object id
* @param name the sequence name
* @param startValue the first value to return
* @param increment the increment count
* @param cacheSize the number of entries to pre-fetch
* @param minValue the minimum value
* @param maxValue the maximum value
* @param cycle whether to jump back to the min value if needed
* @param belongsToTable whether this sequence belongs to a table (for
* auto-increment columns)
*/
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(startValue, this.increment);
this.maxValue = maxValue != null ?
maxValue : getDefaultMaxValue(startValue, 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));
}
}
/**
* 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 (null
if no change)
* @param minValue the new min value (null
if no change)
* @param maxValue the new max value (null
if no change)
* @param increment the new increment (null
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 static long getDefaultMinValue(Long startValue, long increment) {
long v = increment >= 0 ? 1 : Long.MIN_VALUE;
if (startValue != null && increment >= 0 && startValue < v) {
v = startValue;
}
return v;
}
private static long getDefaultMaxValue(Long startValue, long increment) {
long v = increment >= 0 ? Long.MAX_VALUE : -1;
if (startValue != null && increment < 0 && startValue > v) {
v = startValue;
}
return v;
}
private long getDefaultStartValue(long increment) {
return increment >= 0 ? minValue : maxValue;
}
public boolean getBelongsToTable() {
return belongsToTable;
}
public long getIncrement() {
return increment;
}
public long getMinValue() {
return minValue;
}
public long getMaxValue() {
return maxValue;
}
public boolean getCycle() {
return cycle;
}
public void setCycle(boolean cycle) {
this.cycle = cycle;
}
@Override
public String getDropSQL() {
if (getBelongsToTable()) {
return null;
}
return "DROP SEQUENCE IF EXISTS " + getSQL();
}
@Override
public String getCreateSQLForCopy(Table table, String quotedName) {
throw DbException.throwInternalError();
}
@Override
public synchronized String getCreateSQL() {
StringBuilder buff = new StringBuilder("CREATE SEQUENCE ");
buff.append(getSQL()).append(" START WITH ").append(value);
if (increment != 1) {
buff.append(" INCREMENT BY ").append(increment);
}
if (minValue != getDefaultMinValue(value, increment)) {
buff.append(" MINVALUE ").append(minValue);
}
if (maxValue != getDefaultMaxValue(value, increment)) {
buff.append(" MAXVALUE ").append(maxValue);
}
if (cycle) {
buff.append(" CYCLE");
}
if (cacheSize != DEFAULT_CACHE_SIZE) {
buff.append(" CACHE ").append(cacheSize);
}
if (belongsToTable) {
buff.append(" BELONGS_TO_TABLE");
}
return buff.toString();
}
/**
* Get the next value for this sequence.
*
* @param session the session
* @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;
value += increment;
return v;
}
/**
* Flush the current value to disk.
*/
public void flushWithoutMargin() {
if (valueWithMargin != value) {
valueWithMargin = value;
flush(null);
}
}
/**
* Flush the current value, including the margin, to disk.
*
* @param session the session
*/
public synchronized void flush(Session session) {
if (session == null || !database.isSysTableLocked()) {
// This session may not lock the sys table (except if it already has
// locked it) because it must be committed immediately, otherwise
// other threads can not access the sys table.
Session sysSession = database.getSystemSession();
synchronized (sysSession) {
flushInternal(sysSession);
sysSession.commit(false);
}
} else {
synchronized (session) {
flushInternal(session);
}
}
}
private void flushInternal(Session session) {
// just for this case, use the value with the margin for the script
long realValue = value;
try {
value = valueWithMargin;
database.update(session, this);
} finally {
value = realValue;
}
}
/**
* Flush the current value to disk and close this object.
*/
public void close() {
flushWithoutMargin();
}
@Override
public int getType() {
return DbObject.SEQUENCE;
}
@Override
public void removeChildrenAndResources(Session session) {
database.removeMeta(session, getId());
invalidate();
}
@Override
public void checkRename() {
// nothing to do
}
public synchronized long getCurrentValue() {
return value - increment;
}
public void setBelongsToTable(boolean b) {
this.belongsToTable = b;
}
public void setCacheSize(long cacheSize) {
this.cacheSize = Math.max(1, cacheSize);
}
public long getCacheSize() {
return cacheSize;
}
}