| /* Copyright (c) 2001-2010, The HSQL Development Group |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * Neither the name of the HSQL Development Group nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, |
| * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| package org.hsqldb; |
| |
| import java.math.BigDecimal; |
| |
| import org.hsqldb.HsqlNameManager.HsqlName; |
| import org.hsqldb.error.Error; |
| import org.hsqldb.error.ErrorCode; |
| import org.hsqldb.lib.OrderedHashSet; |
| import org.hsqldb.rights.Grantee; |
| import org.hsqldb.store.ValuePool; |
| import org.hsqldb.types.Type; |
| import org.hsqldb.types.Types; |
| |
| /** |
| * Maintains a sequence of numbers. |
| * |
| * @author Fred Toussi (fredt@users dot sourceforge.net) |
| * @version 1.9.0 |
| * @since 1.7.2 |
| */ |
| public final class NumberSequence implements SchemaObject { |
| |
| public final static NumberSequence[] emptyArray = new NumberSequence[]{}; |
| |
| // |
| private HsqlName name; |
| |
| // present value |
| private long currValue; |
| |
| // last value |
| private long lastValue; |
| |
| // limit state |
| private boolean limitReached; |
| |
| // original start value - used in CREATE and ALTER commands |
| private long startValue; |
| private long minValue; |
| private long maxValue; |
| private long increment; |
| private Type dataType; |
| private boolean isCycle; |
| private boolean isAlways; |
| private boolean restartValueDefault; |
| |
| public NumberSequence() { |
| |
| try { |
| setDefaults(null, Type.SQL_BIGINT); |
| } catch (HsqlException e) {} |
| } |
| |
| public NumberSequence(HsqlName name, Type type) { |
| setDefaults(name, type); |
| } |
| |
| public void setDefaults(HsqlName name, Type type) { |
| |
| this.name = name; |
| this.dataType = type; |
| this.name = name; |
| |
| long min; |
| long max; |
| |
| switch (dataType.typeCode) { |
| |
| case Types.TINYINT : |
| max = Byte.MAX_VALUE; |
| min = Byte.MIN_VALUE; |
| break; |
| |
| case Types.SQL_SMALLINT : |
| max = Short.MAX_VALUE; |
| min = Short.MIN_VALUE; |
| break; |
| |
| case Types.SQL_INTEGER : |
| max = Integer.MAX_VALUE; |
| min = Integer.MIN_VALUE; |
| break; |
| |
| case Types.SQL_BIGINT : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| case Types.SQL_NUMERIC : |
| case Types.SQL_DECIMAL : |
| if (type.scale == 0) { |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| |
| break; |
| } |
| |
| // fall through |
| default : |
| throw Error.error(ErrorCode.X_42563); |
| } |
| |
| minValue = min; |
| maxValue = max; |
| increment = 1; |
| } |
| |
| /** |
| * constructor with initial value and increment; |
| */ |
| public NumberSequence(HsqlName name, long value, long increment, |
| Type type) { |
| |
| this(name, type); |
| |
| setStartValue(value); |
| setIncrement(increment); |
| } |
| |
| public int getType() { |
| return SchemaObject.SEQUENCE; |
| } |
| |
| public HsqlName getName() { |
| return name; |
| } |
| |
| public HsqlName getCatalogName() { |
| return name.schema.schema; |
| } |
| |
| public HsqlName getSchemaName() { |
| return name.schema; |
| } |
| |
| public Grantee getOwner() { |
| return name.schema.owner; |
| } |
| |
| public OrderedHashSet getReferences() { |
| return new OrderedHashSet(); |
| } |
| |
| public OrderedHashSet getComponents() { |
| return null; |
| } |
| |
| public void compile(Session session, SchemaObject parentObject) {} |
| |
| public String getSQL() { |
| |
| StringBuffer sb = new StringBuffer(128); |
| |
| if (name == null) { |
| sb.append(Tokens.T_GENERATED).append(' '); |
| |
| if (isAlways()) { |
| sb.append(Tokens.T_ALWAYS); |
| } else { |
| sb.append(Tokens.T_BY).append(' ').append(Tokens.T_DEFAULT); |
| } |
| |
| sb.append(' ').append(Tokens.T_AS).append(' ').append( |
| Tokens.T_IDENTITY).append(Tokens.T_OPENBRACKET); |
| } else { |
| sb.append(Tokens.T_CREATE).append(' '); |
| sb.append(Tokens.T_SEQUENCE).append(' '); |
| sb.append(getName().getSchemaQualifiedStatementName()).append(' '); |
| sb.append(Tokens.T_AS).append(' '); |
| sb.append(getDataType().getNameString()).append(' '); |
| } |
| |
| // |
| sb.append(Tokens.T_START).append(' '); |
| sb.append(Tokens.T_WITH).append(' '); |
| sb.append(startValue); |
| |
| if (getIncrement() != 1) { |
| sb.append(' ').append(Tokens.T_INCREMENT).append(' '); |
| sb.append(Tokens.T_BY).append(' '); |
| sb.append(getIncrement()); |
| } |
| |
| if (!hasDefaultMinMax()) { |
| sb.append(' ').append(Tokens.T_MINVALUE).append(' '); |
| sb.append(getMinValue()); |
| sb.append(' ').append(Tokens.T_MAXVALUE).append(' '); |
| sb.append(getMaxValue()); |
| } |
| |
| if (isCycle()) { |
| sb.append(' ').append(Tokens.T_CYCLE); |
| } |
| |
| if (name == null) { |
| sb.append(Tokens.T_CLOSEBRACKET); |
| } |
| |
| return sb.toString(); |
| } |
| |
| public long getChangeTimestamp() { |
| return 0; |
| } |
| |
| public String getRestartSQL() { |
| |
| StringBuffer sb = new StringBuffer(128); |
| |
| sb.append(Tokens.T_ALTER).append(' '); |
| sb.append(Tokens.T_SEQUENCE); |
| sb.append(' ').append(name.getSchemaQualifiedStatementName()); |
| sb.append(' ').append(Tokens.T_RESTART); |
| sb.append(' ').append(Tokens.T_WITH).append(' ').append(peek()); |
| |
| return sb.toString(); |
| } |
| |
| public static String getRestartSQL(Table t) { |
| |
| String colname = t.getColumn(t.identityColumn).getName().statementName; |
| NumberSequence seq = t.identitySequence; |
| StringBuffer sb = new StringBuffer(128); |
| |
| sb.append(Tokens.T_ALTER).append(' ').append(Tokens.T_TABLE); |
| sb.append(' ').append(t.getName().getSchemaQualifiedStatementName()); |
| sb.append(' ').append(Tokens.T_ALTER).append(' '); |
| sb.append(Tokens.T_COLUMN); |
| sb.append(' ').append(colname); |
| sb.append(' ').append(Tokens.T_RESTART); |
| sb.append(' ').append(Tokens.T_WITH).append(' ').append(seq.peek()); |
| |
| return sb.toString(); |
| } |
| |
| public Type getDataType() { |
| return dataType; |
| } |
| |
| public long getIncrement() { |
| return increment; |
| } |
| |
| public synchronized long getStartValue() { |
| return startValue; |
| } |
| |
| public synchronized long getMinValue() { |
| return minValue; |
| } |
| |
| public synchronized long getMaxValue() { |
| return maxValue; |
| } |
| |
| public synchronized boolean isCycle() { |
| return isCycle; |
| } |
| |
| public synchronized boolean isAlways() { |
| return isAlways; |
| } |
| |
| public synchronized boolean hasDefaultMinMax() { |
| |
| long min; |
| long max; |
| |
| switch (dataType.typeCode) { |
| |
| case Types.TINYINT : |
| max = Byte.MAX_VALUE; |
| min = Byte.MIN_VALUE; |
| break; |
| |
| case Types.SQL_SMALLINT : |
| max = Short.MAX_VALUE; |
| min = Short.MIN_VALUE; |
| break; |
| |
| case Types.SQL_INTEGER : |
| max = Integer.MAX_VALUE; |
| min = Integer.MIN_VALUE; |
| break; |
| |
| case Types.SQL_BIGINT : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| case Types.SQL_NUMERIC : |
| case Types.SQL_DECIMAL : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "NumberSequence"); |
| } |
| |
| return minValue == min && maxValue == max; |
| } |
| |
| synchronized void setStartValue(long value) { |
| |
| if (value < minValue || value > maxValue) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| |
| startValue = value; |
| currValue = lastValue = startValue; |
| } |
| |
| synchronized void setMinValue(long value) { |
| |
| checkInTypeRange(value); |
| |
| if (value >= maxValue || currValue < value) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| |
| minValue = value; |
| } |
| |
| synchronized void setDefaultMinValue() { |
| minValue = getDefaultMinOrMax(false); |
| } |
| |
| synchronized void setMaxValue(long value) { |
| |
| checkInTypeRange(value); |
| |
| if (value <= minValue || currValue > value) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| |
| maxValue = value; |
| } |
| |
| synchronized void setDefaultMaxValue() { |
| maxValue = getDefaultMinOrMax(true); |
| } |
| |
| synchronized void setIncrement(long value) { |
| |
| if (value < Short.MIN_VALUE / 2 || value > Short.MAX_VALUE / 2) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| |
| increment = value; |
| } |
| |
| synchronized void setCurrentValueNoCheck(long value) { |
| |
| checkInTypeRange(value); |
| |
| currValue = lastValue = value; |
| } |
| |
| synchronized void setStartValueNoCheck(long value) { |
| |
| checkInTypeRange(value); |
| |
| startValue = value; |
| currValue = lastValue = startValue; |
| } |
| |
| synchronized void setStartValueDefault() { |
| restartValueDefault = true; |
| } |
| |
| synchronized void setMinValueNoCheck(long value) { |
| |
| checkInTypeRange(value); |
| |
| minValue = value; |
| } |
| |
| synchronized void setMaxValueNoCheck(long value) { |
| |
| checkInTypeRange(value); |
| |
| maxValue = value; |
| } |
| |
| synchronized void setCycle(boolean value) { |
| isCycle = value; |
| } |
| |
| synchronized void setAlways(boolean value) { |
| isAlways = value; |
| } |
| |
| private long getDefaultMinOrMax(boolean isMax) { |
| |
| long min; |
| long max; |
| |
| switch (dataType.typeCode) { |
| |
| case Types.TINYINT : |
| max = Byte.MAX_VALUE; |
| min = Byte.MIN_VALUE; |
| break; |
| |
| case Types.SQL_SMALLINT : |
| max = Short.MAX_VALUE; |
| min = Short.MIN_VALUE; |
| break; |
| |
| case Types.SQL_INTEGER : |
| max = Integer.MAX_VALUE; |
| min = Integer.MIN_VALUE; |
| break; |
| |
| case Types.SQL_BIGINT : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| case Types.SQL_NUMERIC : |
| case Types.SQL_DECIMAL : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "NumberSequence"); |
| } |
| |
| return isMax ? max |
| : min; |
| } |
| |
| private void checkInTypeRange(long value) { |
| |
| long min; |
| long max; |
| |
| switch (dataType.typeCode) { |
| |
| case Types.TINYINT : |
| max = Byte.MAX_VALUE; |
| min = Byte.MIN_VALUE; |
| break; |
| |
| case Types.SQL_SMALLINT : |
| max = Short.MAX_VALUE; |
| min = Short.MIN_VALUE; |
| break; |
| |
| case Types.SQL_INTEGER : |
| max = Integer.MAX_VALUE; |
| min = Integer.MIN_VALUE; |
| break; |
| |
| case Types.SQL_BIGINT : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| case Types.SQL_NUMERIC : |
| case Types.SQL_DECIMAL : |
| max = Long.MAX_VALUE; |
| min = Long.MIN_VALUE; |
| break; |
| |
| default : |
| throw Error.runtimeError(ErrorCode.U_S0500, "NumberSequence"); |
| } |
| |
| if (value < min || value > max) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| } |
| |
| synchronized void checkValues() { |
| |
| if (restartValueDefault) { |
| currValue = lastValue = startValue; |
| restartValueDefault = false; |
| } |
| |
| if (minValue >= maxValue || startValue < minValue |
| || startValue > maxValue || currValue < minValue |
| || currValue > maxValue) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| } |
| |
| synchronized NumberSequence duplicate() { |
| |
| NumberSequence copy = new NumberSequence(); |
| |
| copy.name = name; |
| copy.startValue = startValue; |
| copy.currValue = currValue; |
| copy.lastValue = lastValue; |
| copy.increment = increment; |
| copy.dataType = dataType; |
| copy.minValue = minValue; |
| copy.maxValue = maxValue; |
| copy.isCycle = isCycle; |
| copy.isAlways = isAlways; |
| |
| return copy; |
| } |
| |
| synchronized void reset(NumberSequence other) { |
| |
| name = other.name; |
| startValue = other.startValue; |
| currValue = other.currValue; |
| lastValue = other.lastValue; |
| increment = other.increment; |
| dataType = other.dataType; |
| minValue = other.minValue; |
| maxValue = other.maxValue; |
| isCycle = other.isCycle; |
| isAlways = other.isAlways; |
| } |
| |
| /** |
| * getter for a given value |
| */ |
| synchronized long userUpdate(long value) { |
| |
| if (value == currValue) { |
| currValue += increment; |
| |
| return value; |
| } |
| |
| if (increment > 0) { |
| if (value > currValue) { |
| currValue += ((value - currValue + increment) / increment) |
| * increment; |
| } |
| } else { |
| if (value < currValue) { |
| currValue += ((value - currValue + increment) / increment) |
| * increment; |
| } |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Updates are necessary for text tables |
| * For memory tables, the logged and scripted RESTART WITH will override |
| * this. |
| * No checks as values may have overridden the sequnece defaults |
| */ |
| synchronized long systemUpdate(long value) { |
| |
| if (value == currValue) { |
| currValue += increment; |
| |
| return value; |
| } |
| |
| if (increment > 0) { |
| if (value > currValue) { |
| currValue = value + increment; |
| } |
| } else { |
| if (value < currValue) { |
| currValue = value + increment; |
| } |
| } |
| |
| return value; |
| } |
| |
| synchronized Object getValueObject() { |
| |
| long value = getValue(); |
| Object result; |
| |
| switch (dataType.typeCode) { |
| |
| default : |
| case Types.SQL_SMALLINT : |
| case Types.SQL_INTEGER : |
| result = ValuePool.getInt((int) value); |
| break; |
| |
| case Types.SQL_BIGINT : |
| result = ValuePool.getLong(value); |
| break; |
| |
| case Types.SQL_NUMERIC : |
| case Types.SQL_DECIMAL : |
| result = ValuePool.getBigDecimal(new BigDecimal(value)); |
| break; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * principal getter for the next sequence value |
| */ |
| synchronized public long getValue() { |
| |
| if (limitReached) { |
| throw Error.error(ErrorCode.X_2200H); |
| } |
| |
| long nextValue; |
| |
| if (increment > 0) { |
| if (currValue > maxValue - increment) { |
| if (isCycle) { |
| nextValue = minValue; |
| } else { |
| limitReached = true; |
| nextValue = minValue; |
| } |
| } else { |
| nextValue = currValue + increment; |
| } |
| } else { |
| if (currValue < minValue - increment) { |
| if (isCycle) { |
| nextValue = maxValue; |
| } else { |
| limitReached = true; |
| nextValue = minValue; |
| } |
| } else { |
| nextValue = currValue + increment; |
| } |
| } |
| |
| long result = currValue; |
| |
| currValue = nextValue; |
| |
| return result; |
| } |
| |
| /** |
| * reset to start value |
| */ |
| synchronized void reset() { |
| |
| // no change if called before getValue() or called twice |
| lastValue = currValue = startValue; |
| } |
| |
| /** |
| * get next value without incrementing |
| */ |
| synchronized public long peek() { |
| return currValue; |
| } |
| |
| /** |
| * reset the wasUsed flag |
| */ |
| synchronized boolean resetWasUsed() { |
| |
| boolean result = lastValue != currValue; |
| |
| lastValue = currValue; |
| |
| return result; |
| } |
| |
| /** |
| * reset to new initial value |
| */ |
| synchronized public void reset(long value) { |
| |
| if (value < minValue || value > maxValue) { |
| throw Error.error(ErrorCode.X_42597); |
| } |
| |
| startValue = currValue = lastValue = value; |
| } |
| } |