blob: 5bd9673af804d3975babe7176789053f8a60baa0 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.catalog.SequenceUpdater
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.derby.impl.sql.catalog;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import org.apache.derby.catalog.SequencePreallocator;
import org.apache.derby.iapi.db.Database;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Property;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.cache.Cacheable;
import org.apache.derby.iapi.services.cache.CacheManager;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.context.Context;
import org.apache.derby.iapi.services.context.ContextService;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.iapi.services.property.PropertyUtil;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.dictionary.BulkInsertCounter;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.SequenceDescriptor;
import org.apache.derby.iapi.store.access.AccessFactory;
import org.apache.derby.iapi.store.access.TransactionController;
import org.apache.derby.iapi.types.NumberDataValue;
import org.apache.derby.iapi.types.RowLocation;
/**
* <p>
* An object cached in the data dictionary which manages new values
* for sequences. Note that this class must be public and
* have a 0-arg constructor in order to satisfy the Cacheable contract.
* </p>
*
* <p>
* This is the abstract superclass of specific implementations for specific
* sequences. For instance, one subclass handles the ANSI/ISO sequences
* stored in SYSSEQUENCES. Another subclass could handle the sequences
* stored in Derby's identity columns.
* </p>
*
* <p>
* This class does a couple tricky things:
* </p>
*
* <ul>
* <li>It pre-allocates a range of values from a sequence so that we don't have to change
* the on-disk value every time we get the next value for a sequence.</li>
* <li>When updating the on-disk value, we use a subtransaction of the user's
* execution transaction. If the
* special transaction cannot do its work immediately, without waiting for a lock, then
* a TOO MUCH CONTENTION error is raised. It is believed that this can only happen
* if someone holds locks on SYSSEQUENCES, either via sequence DDL or a scan
* of the catalog. The TOO MUCH CONTENTION error tells
* the user to not scan SYSSEQUENCES directly, but to instead use the
* SYSCS_UTIL.SYSCS_PEEK_AT_SEQUENCE() if the user needs the current value of the
* sequence generator.</li>
* </ul>
*
* <p>
* Here is the algorithm pursued when the caller asks for the next number in a sequence:
* </p>
*
*
* <ul>
* <li>We try to get the next number from a cache of pre-allocated numbers. The endpoint
* (last number in the pre-allocated range) was previously recorded in the catalog row which
* describes this sequence. If we are successful in getting the next number, we
* return it and all is well.</li>
* <li>Otherwise, we must allocate a new range by updating the catalog row. We should not
* be in contention with another connection because the update method is synchronized.</li>
* </ul>
*
*/
public abstract class SequenceUpdater implements Cacheable
{
///////////////////////////////////////////////////////////////////////////////////
//
// CONSTANTS
//
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
//
// CONSTANT STATE
//
///////////////////////////////////////////////////////////////////////////////////
// DataDictionary where this generator is cached.
protected DataDictionaryImpl _dd;
//
// The following state needs to be reset whenever this Cachable is re-used.
//
/** This is the key used to lookup this generator in the cache. */
protected String _uuidString;
/** This is the object which allocates ranges of sequence values */
protected SequenceGenerator _sequenceGenerator;
///////////////////////////////////////////////////////////////////////////////////
//
// CONSTRUCTOR
//
///////////////////////////////////////////////////////////////////////////////////
/** No-arg constructor to satisfy the Cacheable contract */
public SequenceUpdater()
{
}
/** Normal constructor */
public SequenceUpdater( DataDictionaryImpl dd )
{
this();
_dd = dd;
}
///////////////////////////////////////////////////////////////////////////////////
//
// ABSTRACT OR OVERRIDABLE BEHAVIOR TO BE IMPLEMENTED BY CHILDREN
//
///////////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Initialize the sequence generator. Work is done inside a read-only subtransaction of
* the session's execution transaction.
* </p>
*/
abstract protected SequenceGenerator createSequenceGenerator( TransactionController readOnlyTC )
throws StandardException;
/**
* <p>
* Update the sequence value on disk. This method does its work in a subtransaction of
* the user's execution transaction.
* </p>
*
* @param tc The transaction to use
* @param oldValue Expected value on disk for this sequence
* @param newValue The value to poke into the system table backing this sequence
* @param wait Whether to wait for a lock
*
*
* @return Returns true if the value was successfully updated, false if we lost a race with another session.
*
* @throws StandardException May throw an exception if a lock can't be obtained.
*/
abstract protected boolean updateCurrentValueOnDisk( TransactionController tc, Long oldValue, Long newValue, boolean wait ) throws StandardException;
/**
* <p>
* Create an exception to state that there is too much contention on the generator.
* For backward compatibility reasons, different messages are needed by sequences
* and identities. See DERBY-5426.
* </p>
*/
private StandardException tooMuchContentionException()
{
// If the sequence lives in the SYS schema, then it is used to back an identity column.
if ( SchemaDescriptor.STD_SYSTEM_SCHEMA_NAME.equals( _sequenceGenerator.getSchemaName() ) )
{
return StandardException.newException( SQLState.LOCK_TIMEOUT );
}
else
{
return StandardException.newException
( SQLState.LANG_TOO_MUCH_CONTENTION_ON_SEQUENCE, _sequenceGenerator.getName() );
}
}
///////////////////////////////////////////////////////////////////////////////////
//
// Cacheable BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
public void clean(boolean forRemove) throws StandardException
{
//
// Flush current value to disk. This prevents us from leaking values when DDL
// is performed. The metadata caches are invalidated and cleared when DDL is performed.
// We flush the current value to disk on database shutdown also.
// The call to updateCurrentValueOnDisk can fail if someone is holding a lock
// on the SYS.SYSSEQUENCES row. This can happen if the user disregards our
// advice and scans that catalog. This can also happen if the transaction which
// creates the sequences is open for a long time and some later sequence creation
// causes us to evict the old, uncommitted sequence generator from the cache.
//
boolean gapClosed = false;
try {
if ( _sequenceGenerator == null ) { gapClosed = true; }
else
{
gapClosed = updateCurrentValueOnDisk( null, peekAtCurrentValue() );
}
}
catch (StandardException se)
{
// The too much contention exception is redundant because the problem is logged
// by the message below
if ( !SQLState.LANG_TOO_MUCH_CONTENTION_ON_SEQUENCE.equals( se.getMessageId() ) )
{
throw se;
}
}
finally
{
// log an error message if we failed to flush the preallocated values.
if ( !gapClosed )
{
String errorMessage = MessageService.getTextMessage
(
SQLState.LANG_CANT_FLUSH_PREALLOCATOR,
_sequenceGenerator.getSchemaName(),
_sequenceGenerator.getName()
);
Monitor.getStream().println( errorMessage );
}
_uuidString = null;
_sequenceGenerator = null;
}
}
public boolean isDirty() { return false; }
public Object getIdentity() { return _uuidString; }
public void clearIdentity()
{
try
{
clean( false );
} catch (StandardException se)
{
//Doing check for lcc and db to be certain
LanguageConnectionContext lcc = getLCC();
if (lcc != null)
{
Database db = lcc.getDatabase();
boolean isactive = (db != null ? db.isActive() : false);
lcc.getContextManager().cleanupOnError(se, isactive);
}
}
}
public Cacheable createIdentity( Object key, Object createParameter ) throws StandardException
{
Cacheable cacheable = this;
//
// The createParameter arg is unused.
//
return cacheable.setIdentity( key );
}
/**
* @see Cacheable#setIdentity
*
* @exception StandardException Thrown on error
*/
public Cacheable setIdentity(Object key) throws StandardException
{
if (SanityManager.DEBUG)
{
if (!(key instanceof String))
{
SanityManager.THROWASSERT( "Key for a SequenceUpdater is a " + key.getClass().getName() );
}
if ( (_uuidString != null) || (_sequenceGenerator != null) )
{
SanityManager.THROWASSERT( "Identity being changed on a live cacheable. Old uuidString = " + _uuidString );
}
}
_uuidString = (String) key;
if ( _sequenceGenerator == null )
{
TransactionController executionTC = getLCC().getTransactionExecute();
//
// We lookup information in a read-only subtransaction in order to minimize
// contention. Since this is a read-only subtransaction, there should be
// no conflict with the parent transaction.
//
TransactionController subTransaction =
executionTC.startNestedUserTransaction( true, true );
try {
_sequenceGenerator = createSequenceGenerator( subTransaction );
}
finally
{
// if we failed to get a generator, we have no identity. see DERBY-5389.
if ( _sequenceGenerator == null ) { _uuidString = null; }
subTransaction.commit();
subTransaction.destroy();
}
}
if ( _sequenceGenerator != null ) { return this; }
else { return null; }
}
///////////////////////////////////////////////////////////////////////////////////
//
// PUBLIC BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Reset the sequence generator to a new start value. This is used by the special
* bulk-insert optimization in InsertResultSet.
* </p>
*/
public synchronized void reset( Long newValue )
throws StandardException
{
// first try to reset on disk
updateCurrentValueOnDisk( null, newValue );
// now reset the sequence generator
_sequenceGenerator = _sequenceGenerator.clone( newValue );
}
/**
* <p>
* Get the SequenceUpdater used for the bulk-insert optimization in InsertResultSet.
* </p>
*
* @param restart True if the counter should be re-initialized to its start position.
*/
public synchronized BulkInsertUpdater getBulkInsertUpdater( boolean restart )
throws StandardException
{
return new BulkInsertUpdater( this, restart );
}
/**
* <p>
* Get the next sequence number managed by this generator and advance the number. Could raise an
* exception if the legal range is exhausted and wrap-around is not allowed.
* Only one thread at a time is allowed through here. We do not want a race between the
* two calls to the sequence generator: getCurrentValueAndAdvance() and allocateNewRange().
* </p>
*
* @param returnValue This value is stuffed with the new sequence number.
*/
public synchronized void getCurrentValueAndAdvance
( NumberDataValue returnValue ) throws StandardException
{
//
// We may have to try to get a value from the Sequence Generator twice.
// The first attempt may fail because we need to pre-allocate a new chunk
// of values.
//
for ( int i = 0; i < 2; i++ )
{
//
// We try to get a sequence number. The SequenceGenerator method is synchronized
// so only one writer should be in there at a time. Lock contention is possible if
// someone has selected from SYSSEQUENCES contrary to our advice. In that case,
// we raise a TOO MUCH CONTENTION exception.
//
long[] cvaa = _sequenceGenerator.getCurrentValueAndAdvance();
int status = (int) cvaa[ SequenceGenerator.CVAA_STATUS ];
long currentValue = cvaa[ SequenceGenerator.CVAA_CURRENT_VALUE ];
long lastAllocatedValue = cvaa[ SequenceGenerator.CVAA_LAST_ALLOCATED_VALUE ];
long numberOfValuesAllocated = cvaa[ SequenceGenerator.CVAA_NUMBER_OF_VALUES_ALLOCATED ];
switch ( status )
{
case SequenceGenerator.RET_OK:
returnValue.setValue( currentValue );
return;
case SequenceGenerator.RET_MARK_EXHAUSTED:
updateCurrentValueOnDisk( currentValue, null );
returnValue.setValue( currentValue );
return;
case SequenceGenerator.RET_ALLOCATE_NEW_VALUES:
if ( updateCurrentValueOnDisk( currentValue, lastAllocatedValue ) )
{
_sequenceGenerator.allocateNewRange( currentValue, numberOfValuesAllocated );
}
break;
default:
throw unimplementedFeature();
}
}
//
// If we get here, then we failed to allocate a new sequence number range.
//
throw tooMuchContentionException();
}
/**
* <p>
* Get the current value of the sequence generator without advancing it.
* May return null if the generator is exhausted.
* </p>
*/
public Long peekAtCurrentValue() throws StandardException
{
return _sequenceGenerator.peekAtCurrentValue();
}
///////////////////////////////////////////////////////////////////////////////////
//
// DISK WRITING MINIONS
//
///////////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Update the value on disk. Does its work in a subtransaction of the user's
* execution transaction. If that fails, raises a TOO MUCH CONTENTION exception.
* </p>
*
* @return Returns true if the value was successfully updated, false if we lost a race with another session.
*
*/
public synchronized boolean updateCurrentValueOnDisk( Long oldValue, Long newValue ) throws StandardException
{
LanguageConnectionContext lcc = getLCC();
//
// Not having an LCC should mean that we are in the middle of engine
// shutdown. We get here only to flush the current value to disk so that
// we don't leak unused sequence numbers. See DERBY-5398.
//
if ( lcc == null )
{
if (SanityManager.DEBUG)
{
SanityManager.ASSERT( oldValue == null, "We should be flushing unused sequence values here." );
}
ContextService csf = getContextService();
ContextManager cm = csf.getCurrentContextManager();
AccessFactory af = _dd.af;
TransactionController dummyTransaction = af.getTransaction( cm );
boolean retval = updateCurrentValueOnDisk( dummyTransaction, oldValue, newValue, false );
dummyTransaction.commit();
dummyTransaction.destroy();
return retval;
}
TransactionController executionTransaction = lcc.getTransactionExecute();
TransactionController nestedTransaction =
executionTransaction.startNestedUserTransaction( false, true );
if ( nestedTransaction != null )
{
boolean retval = false;
boolean escalateToParentTransaction = false;
try
{
retval = updateCurrentValueOnDisk( nestedTransaction, oldValue, newValue, false );
}
catch (StandardException se)
{
if ( !se.isLockTimeout() )
{
if ( se.isSelfDeadlock() )
{
// We're blocked by a lock held by our parent transaction.
// Escalate into the parent transaction now. See DERBY-6554.
escalateToParentTransaction = true;
}
else
{
Monitor.logThrowable( se );
throw se;
}
}
}
finally
{
// DERBY-5494, if this commit does not flush log then an
// unorderly shutdown could lose the update. Do not use
// commitNoSync(), and store needs to flush user nested update
// transaction commits by default.
nestedTransaction.commit();
nestedTransaction.destroy();
if ( escalateToParentTransaction )
{
retval = updateCurrentValueOnDisk( executionTransaction, oldValue, newValue, false );
}
return retval;
}
}
// If we get here, we failed to do the work in the nested transaction.
// We might be self-deadlocking if the user has selected from SYSSEQUENCES
// contrary to our advice.
throw tooMuchContentionException();
}
///////////////////////////////////////////////////////////////////////////////////
//
// UTILITY MINIONS
//
///////////////////////////////////////////////////////////////////////////////////
/** Make a new range allocator (called when the generator is instantiated) */
protected SequencePreallocator makePreallocator( TransactionController tc )
throws StandardException
{
String propertyName = Property.LANG_SEQUENCE_PREALLOCATOR;
String className = PropertyUtil.getServiceProperty( tc, propertyName );
if ( className == null ) { return new SequenceRange(); }
try {
// If the property value was a number rather than a class name, then
// use that as the default size for preallocated ranges.
if ( isNumber( className ) )
{
return new SequenceRange( Integer.parseInt( className ) );
}
Class<?> klass = Class.forName(className);
if (!SequencePreallocator.class.isAssignableFrom(klass)) {
throw StandardException.newException(
SQLState.LANG_NOT_A_SEQUENCE_PREALLOCATOR, propertyName);
}
return (SequencePreallocator) klass.getConstructor().newInstance();
}
catch (ClassNotFoundException e) { throw missingAllocator( propertyName, className, e ); }
catch (ClassCastException e) { throw missingAllocator( propertyName, className, e ); }
catch (InstantiationException e) { throw missingAllocator( propertyName, className, e ); }
catch (IllegalAccessException e) { throw missingAllocator( propertyName, className, e ); }
catch (NumberFormatException e) { throw missingAllocator( propertyName, className, e ); }
catch (NoSuchMethodException e) { throw missingAllocator( propertyName, className, e ); }
catch (java.lang.reflect.InvocationTargetException e) { throw missingAllocator( propertyName, className, e ); }
}
private StandardException missingAllocator( String propertyName, String className, Exception e )
{
return StandardException.newException( SQLState.LANG_UNKNOWN_SEQUENCE_PREALLOCATOR, e, propertyName, className );
}
private boolean isNumber( String text )
{
int length = text.length();
for ( int i = 0; i < length; i++ )
{
if ( !Character.isDigit( text.charAt( i ) ) ) { return false; }
}
return true;
}
private static LanguageConnectionContext getLCC()
{
return (LanguageConnectionContext)
getContextOrNull(LanguageConnectionContext.CONTEXT_ID);
}
/** Report an unimplemented feature */
private StandardException unimplementedFeature()
{
return StandardException.newException( SQLState.BTREE_UNIMPLEMENTED_FEATURE );
}
/**
* Privileged lookup of the ContextService. Must be private so that user code
* can't call this entry point.
*/
private static ContextService getContextService()
{
if ( System.getSecurityManager() == null )
{
return ContextService.getFactory();
}
else
{
return AccessController.doPrivileged
(
new PrivilegedAction<ContextService>()
{
public ContextService run()
{
return ContextService.getFactory();
}
}
);
}
}
/**
* Privileged lookup of a Context. Must be private so that user code
* can't call this entry point.
*/
private static Context getContextOrNull( final String contextID )
{
if ( System.getSecurityManager() == null )
{
return ContextService.getContextOrNull( contextID );
}
else
{
return AccessController.doPrivileged
(
new PrivilegedAction<Context>()
{
public Context run()
{
return ContextService.getContextOrNull( contextID );
}
}
);
}
}
///////////////////////////////////////////////////////////////////////////////////
//
// INNER CLASSES
//
///////////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Specific implementation of SequenceUpdater for the sequences managed by
* SYSSEQUENCES.
* </p>
*/
public static final class SyssequenceUpdater extends SequenceUpdater
{
private RowLocation _sequenceRowLocation;
public SyssequenceUpdater() { super(); }
public SyssequenceUpdater( DataDictionaryImpl dd ) { super( dd ); }
//
// SequenceUpdater BEHAVIOR
//
protected SequenceGenerator createSequenceGenerator( TransactionController readOnlyTC )
throws StandardException
{
RowLocation[] rowLocation = new RowLocation[ 1 ];
SequenceDescriptor[] sequenceDescriptor = new SequenceDescriptor[ 1 ];
_dd.computeSequenceRowLocation( readOnlyTC, _uuidString, rowLocation, sequenceDescriptor );
_sequenceRowLocation = rowLocation[ 0 ];
SequenceDescriptor isd = sequenceDescriptor[ 0 ];
return new SequenceGenerator
(
isd.getCurrentValue(),
isd.canCycle(),
isd.getIncrement(),
isd.getMaximumValue(),
isd.getMinimumValue(),
isd.getStartValue(),
isd.getSchemaDescriptor().getSchemaName(),
isd.getSequenceName(),
makePreallocator( readOnlyTC )
);
}
protected boolean updateCurrentValueOnDisk( TransactionController tc, Long oldValue, Long newValue, boolean wait ) throws StandardException
{
return _dd.updateCurrentSequenceValue( tc, _sequenceRowLocation, wait, oldValue, newValue );
}
}
/**
* <p>
* Implementation of SequenceUpdater for use with the bulk-insert optimization
* used by InsertResultSet. This BulkInsertUpdater doesn't really write to disk. It is assumed
* that the BulkInsertUpdater will only be used by the bulk-insert code, where the
* user has exclusive write-access on the table whose identity column is backed by
* the original SequenceUpdater. At the end of bulk-insert, the current value of the
* BulkInsertUpdater is written to disk by other code.
* </p>
*/
public static final class BulkInsertUpdater extends SequenceUpdater implements BulkInsertCounter
{
public BulkInsertUpdater() { super(); }
public BulkInsertUpdater( SequenceUpdater originalUpdater, boolean restart )
{
_sequenceGenerator = originalUpdater._sequenceGenerator.clone( restart );
}
//
// SequenceUpdater BEHAVIOR
//
protected SequenceGenerator createSequenceGenerator( TransactionController readOnlyTC )
throws StandardException
{
return _sequenceGenerator;
}
protected boolean updateCurrentValueOnDisk( TransactionController tc, Long oldValue, Long newValue, boolean wait )
throws StandardException
{
// always succeeds
return true;
}
}
}