444 |
444 |
** were added to the collection during iterating it, resulting in a
|
445 |
445 |
** ConcurrentModificationException (refs #6356).
|
446 |
446 |
** CA 20220707 Cache the converted Java names for the dynamic buffers.
|
|
447 |
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the
|
|
448 |
** registration is now specific to each type of scopeable. For each case, the block
|
|
449 |
** will be registered for scope support (for that particular scopeable) only when
|
|
450 |
** the scopeable is 'active' (i.e. unnamed streams or accumulators are used). This
|
|
451 |
** allows a lazy registration of scopeables, to avoid the unnecessary overhead of
|
|
452 |
** processing all the scopeables for each and every block.
|
|
453 |
** CA 20220906 Removed state which is no longer used (PersistentProcScope.loadedBuffers and
|
|
454 |
** undoData) and removed methods which are not being invoked.
|
|
455 |
** Moved the parameter assigner scopeable support to AbstractParameter (as all
|
|
456 |
** parameters require scope support, not just fields).
|
|
457 |
** Extracted all BufferManager state which requires notification for
|
|
458 |
** 'isImportantBlock' to an external TxWrapper class (including the private class
|
|
459 |
** here), to allow the scopeable BufferManager state related only to buffers (and
|
|
460 |
** not transaction related) to be registered for scope notifications in a lazy
|
|
461 |
** manner, when buffers are accessed/used/opened.
|
447 |
462 |
*/
|
448 |
463 |
|
449 |
464 |
/*
|
... | ... | |
512 |
527 |
import com.goldencode.p2j.oo.lang.*;
|
513 |
528 |
import com.goldencode.p2j.persist.dirty.*;
|
514 |
529 |
import com.goldencode.p2j.persist.id.*;
|
515 |
|
import com.goldencode.p2j.persist.lock.*;
|
516 |
|
import com.goldencode.p2j.persist.orm.*;
|
517 |
530 |
import com.goldencode.p2j.security.*;
|
518 |
531 |
import com.goldencode.p2j.util.*;
|
519 |
532 |
import com.goldencode.p2j.util.LogHelper;
|
520 |
533 |
import com.goldencode.p2j.util.ErrorManager;
|
521 |
534 |
import com.goldencode.util.*;
|
|
535 |
import com.goldencode.util.Stack;
|
522 |
536 |
|
523 |
537 |
/**
|
524 |
538 |
* Manages all record buffers within the current user context. This includes
|
... | ... | |
553 |
567 |
*/
|
554 |
568 |
public final class BufferManager
|
555 |
569 |
implements Scopeable,
|
556 |
|
Commitable,
|
557 |
570 |
BatchListener,
|
558 |
|
StopConditionVetoHandler,
|
559 |
|
Resettable
|
|
571 |
StopConditionVetoHandler
|
560 |
572 |
{
|
561 |
573 |
/** Logger */
|
562 |
574 |
private static final Logger LOG = LogHelper.getLogger(BufferManager.class.getName());
|
... | ... | |
568 |
580 |
@Override protected BufferManager initialValue() { return (new BufferManager()); }
|
569 |
581 |
};
|
570 |
582 |
|
571 |
|
/** Flag indicating whether class was initialized, i.e. {@link #initialize} was called */
|
572 |
|
private static boolean initialized = false;
|
573 |
|
|
574 |
583 |
/** A cache of buffer-related fields defined in a specific legacy-converted class. */
|
575 |
584 |
private static final Map<Class<?>, Set<Field>> classBufferFields = new ConcurrentHashMap<>();
|
576 |
585 |
|
... | ... | |
578 |
587 |
// intentionally package private for direct use from RecordBuffer
|
579 |
588 |
final Map<BufferType, MutableInteger> changeScopes = new HashMap<>();
|
580 |
589 |
|
581 |
|
/** Transaction finalizable to manage transaction state locally (avoid context-local calls) */
|
582 |
|
private final Finalizable xactFin = new Finalizable()
|
583 |
|
{
|
584 |
|
@Override public void finished() { transactionDepth = -1; }
|
585 |
|
@Override public void deleted() { }
|
586 |
|
@Override public void iterate() { }
|
587 |
|
@Override public void retry() { }
|
588 |
|
};
|
589 |
|
|
590 |
590 |
/** Finalizable which clears batch mode at the appropriate times */
|
591 |
591 |
private final Finalizable batchCleaner = new Finalizable()
|
592 |
592 |
{
|
... | ... | |
595 |
595 |
@Override public void iterate() { RecordBuffer.cleanupBatchMode(BufferManager.this); }
|
596 |
596 |
@Override public void retry() { RecordBuffer.cleanupBatchMode(BufferManager.this); }
|
597 |
597 |
};
|
598 |
|
|
599 |
|
/** Field assigner for function/procedure parameter assign-backs */
|
600 |
|
private final Scopeable fieldScopes = FieldAssigner.getScopeable();
|
601 |
|
|
|
598 |
|
602 |
599 |
/** The buffers which will be registered on the next scope entry. */
|
603 |
600 |
private final Set<RecordBuffer> pendingBuffers = Collections.newSetFromMap(new IdentityHashMap<>());
|
604 |
601 |
|
... | ... | |
622 |
619 |
*/
|
623 |
620 |
private final ScopedDictionary<Object, Map<String, RecordBuffer>> byLegacyName = new ScopedDictionary<>();
|
624 |
621 |
|
625 |
|
/**
|
626 |
|
* Scoped dictionary which key set contains all buffers into which a new record was loaded (or
|
627 |
|
* the old record was reloaded) into corresponding scopes. Value set doesn't contain any useful
|
628 |
|
* information and is used as a temporary data storage during copying from one scope level to
|
629 |
|
* another.
|
630 |
|
*/
|
631 |
|
private final ScopedDictionary<RecordBuffer, Boolean> loadedBuffers = new ScopedDictionary<>();
|
632 |
|
|
633 |
622 |
/** Buffers whose scopes have been opened, but are not yet initialized */
|
634 |
623 |
private final Map<RecordBuffer, Boolean> uninitializedOpenBuffers = new WeakHashMap<>();
|
635 |
624 |
|
... | ... | |
637 |
626 |
private final ScopedList<RecordBuffer> openBuffers = new ScopedList<>();
|
638 |
627 |
|
639 |
628 |
/**
|
640 |
|
* The buffers touched via a Create, Update or Delete operation, in each scope. If the full transaction
|
641 |
|
* block is not {@link #commit committed} or {@link #rollback rolled back}, then the buffers will be merged
|
642 |
|
* in the previous scope.
|
643 |
|
*/
|
644 |
|
private final TailScopedDictionary<RecordBuffer, Boolean> dirtyBuffers = new TailScopedDictionary<>();
|
645 |
|
|
646 |
|
/**
|
647 |
629 |
* Scoped dictionary which contains currently active static temp tables (i.e. table scope was
|
648 |
630 |
* opened and not yet closed). Keyed by a {@code TempTableKey} compound from the legacy table
|
649 |
631 |
* name and the procedure where the temp table was defined.
|
... | ... | |
662 |
644 |
private final ScopedDictionary<OutputTableHandleCopier, Integer> thOutputParameters =
|
663 |
645 |
new ScopedDictionary<>();
|
664 |
646 |
|
665 |
|
/** Database to transaction wrapper instance map */
|
666 |
|
private final Map<Database, TxWrapper> txWrapperMap = new HashMap<>();
|
667 |
|
|
668 |
|
/** Transaction wrappers not yet activated */
|
669 |
|
private final Map<Database, TxWrapper> inactiveTxWrappers = new HashMap<>();
|
|
647 |
/** Track the external scopes pushed on {@link BufferManager}. */
|
|
648 |
private final Stack<Boolean> externalScope = new Stack<>();
|
670 |
649 |
|
671 |
650 |
/** Active persistence contexts (those which have an open database session) */
|
672 |
651 |
private final Set<Persistence.Context> activePersistenceContexts = Collections.newSetFromMap(new IdentityHashMap<>());
|
673 |
652 |
|
674 |
|
/** All undoable actions scoped by block and keyed by buffer type */
|
675 |
|
private final ScopedDictionary<BufferType, UndoData> undoData = new ScopedDictionary<>();
|
676 |
|
|
677 |
653 |
/** Stack of batch mode data */
|
678 |
654 |
private final ArrayDeque<BatchModeData> batchModeStack = new ArrayDeque<>();
|
679 |
655 |
|
... | ... | |
686 |
662 |
/** Helper to use the ProcedureManager without any context local lookups. */
|
687 |
663 |
private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
|
688 |
664 |
|
689 |
|
/** Counter of in-use buffers for each database. */
|
690 |
|
private final Map<Database, MutableInteger> activeDatabases = new HashMap<>();
|
691 |
|
|
692 |
665 |
/** Transaction helper */
|
693 |
666 |
private final TransactionManager.TransactionHelper txHelper;
|
694 |
667 |
|
695 |
|
/** Block depth at which full application transaction began, or -1 if not in a transaction */
|
696 |
|
private int transactionDepth = -1;
|
|
668 |
/** Tx wrapper helper. */
|
|
669 |
private TxWrapper.TxWrapperHelper txWrapperHelper;
|
|
670 |
|
|
671 |
/** Change broker instance. */
|
|
672 |
private final ChangeBroker changeBroker;
|
697 |
673 |
|
698 |
674 |
/** Connection manager */
|
699 |
675 |
private ConnectionManager connMgr = null;
|
700 |
676 |
|
701 |
|
/** Context-local access to unique constraint tracker */
|
702 |
|
private UniqueTracker.Context uniqueTrackerCtx = UniqueTracker.getContext();
|
703 |
|
|
704 |
677 |
/** Number of nested queries being processed */
|
705 |
678 |
private int queryDepth = 0;
|
706 |
679 |
|
707 |
|
/** Counter of the number of scope transitions (push or pop) */
|
708 |
|
private long scopeTransitions = 0L;
|
709 |
|
|
710 |
|
/** Counter of the number of application-level transactions */
|
711 |
|
private long transactionCount = 0L;
|
712 |
|
|
713 |
680 |
/** Map of bound buffers to there destination, for each procedure. */
|
714 |
681 |
private Map<TemporaryBuffer, Map<Object, Set<TemporaryBuffer>>> bindingBuffers = new IdentityHashMap<>();
|
715 |
682 |
|
... | ... | |
732 |
699 |
private BufferManager()
|
733 |
700 |
{
|
734 |
701 |
byLegacyName.setIdentityKeys(true);
|
735 |
|
loadedBuffers.setIdentityKeys(true);
|
736 |
|
dirtyBuffers.setIdentityKeys(true);
|
737 |
702 |
thOutputParameters.setIdentityKeys(true);
|
738 |
703 |
|
739 |
704 |
txHelper = TransactionManager.getTransactionHelper();
|
740 |
705 |
|
741 |
706 |
txHelper.registerBatchListener(this);
|
742 |
707 |
txHelper.registerStopVetoHandler(this);
|
743 |
|
txHelper.registerResettable(this);
|
|
708 |
|
|
709 |
changeBroker = ChangeBroker.get();
|
744 |
710 |
|
745 |
711 |
if (LOG.isLoggable(Level.FINE))
|
746 |
712 |
{
|
... | ... | |
749 |
715 |
}
|
750 |
716 |
|
751 |
717 |
/**
|
752 |
|
* Retrieve the context-local instance of this class, instantiating it
|
753 |
|
* first if necessary.
|
|
718 |
* Retrieve the context-local instance of this class, instantiating it first if necessary.
|
754 |
719 |
*
|
755 |
720 |
* @return Context-local instance of this class.
|
756 |
|
* @throws IllegalStateException
|
757 |
|
* when {@link BufferManager} is not activated, i.e. {@link
|
758 |
|
* #initialize()} is not called.
|
759 |
721 |
*/
|
760 |
722 |
public static BufferManager get()
|
761 |
723 |
{
|
762 |
|
if (!BufferManager.initialized)
|
763 |
|
{
|
764 |
|
throw new IllegalStateException(
|
765 |
|
"An attempt to use uninitialized BufferManager. Is persistence activated?");
|
766 |
|
}
|
767 |
|
|
768 |
724 |
BufferManager bm = context.get();
|
769 |
725 |
if (bm.connMgr == null)
|
770 |
726 |
{
|
... | ... | |
779 |
735 |
throw new RuntimeException("Multiple ConnectionManager in same context.");
|
780 |
736 |
}
|
781 |
737 |
}
|
|
738 |
|
|
739 |
if (bm.txWrapperHelper == null)
|
|
740 |
{
|
|
741 |
bm.txWrapperHelper = TxWrapper.getHelper(bm);
|
|
742 |
}
|
|
743 |
|
782 |
744 |
return bm;
|
783 |
745 |
}
|
784 |
746 |
|
785 |
747 |
/**
|
|
748 |
* Register the {@link BufferManager} instance as for scope notifications at the current block.
|
|
749 |
*
|
|
750 |
* @param bufferManager
|
|
751 |
* The {@link BufferManager} instance.
|
|
752 |
*/
|
|
753 |
public static void registerScopeable(BufferManager bufferManager)
|
|
754 |
{
|
|
755 |
if (bufferManager == null)
|
|
756 |
{
|
|
757 |
bufferManager = get();
|
|
758 |
}
|
|
759 |
|
|
760 |
// both ChangeBroker and BufferManager need to be added
|
|
761 |
bufferManager.txHelper.registerBlockScopeable(bufferManager);
|
|
762 |
bufferManager.txHelper.registerBlockScopeable(bufferManager.changeBroker);
|
|
763 |
}
|
|
764 |
|
|
765 |
/**
|
|
766 |
* Register the {@link BufferManager} instance as pending scopeable, to be added to the next block.
|
|
767 |
*
|
|
768 |
* @param bufferManager
|
|
769 |
* The {@link BufferManager} instance.
|
|
770 |
*/
|
|
771 |
public static void registerPendingScopeable(BufferManager bufferManager)
|
|
772 |
{
|
|
773 |
if (bufferManager == null)
|
|
774 |
{
|
|
775 |
bufferManager = get();
|
|
776 |
}
|
|
777 |
|
|
778 |
// both ChangeBroker and BufferManager need to be added
|
|
779 |
bufferManager.txHelper.registerPendingScopeable(bufferManager);
|
|
780 |
bufferManager.txHelper.registerPendingScopeable(bufferManager.changeBroker);
|
|
781 |
}
|
|
782 |
|
|
783 |
/**
|
|
784 |
* Register the {@link TxWrapper} scopeable as pending for the next opened block.
|
|
785 |
*
|
|
786 |
* @param bufferManager
|
|
787 |
* The {@link BufferManager} instance.
|
|
788 |
*/
|
|
789 |
public static void registerTxScopeable(BufferManager bufferManager)
|
|
790 |
{
|
|
791 |
if (bufferManager == null)
|
|
792 |
{
|
|
793 |
bufferManager = get();
|
|
794 |
}
|
|
795 |
|
|
796 |
bufferManager.txWrapperHelper.registerPendingScopeable();
|
|
797 |
}
|
|
798 |
|
|
799 |
/**
|
786 |
800 |
* Cleanup of pending resources. This is called when the external procedure constructor failed
|
787 |
801 |
* to build a new object because of an exception thrown in a member initialization (shared
|
788 |
802 |
* frame/table).
|
... | ... | |
794 |
808 |
bm.pendingBufferClasses.clear();
|
795 |
809 |
bm.pendingStaticTempTables.clear();
|
796 |
810 |
}
|
797 |
|
|
798 |
|
/**
|
799 |
|
* Register with the {@link com.goldencode.p2j.util.TransactionManager
|
800 |
|
* TransactionManager} a factory object which creates instances of this
|
801 |
|
* class, so that they can be registered to receive notifications of
|
802 |
|
* runtime scope start and finish events.
|
803 |
|
* <p>
|
804 |
|
* This method should be invoked once during the server bootstrap phase.
|
805 |
|
*/
|
806 |
|
static void initialize()
|
807 |
|
{
|
808 |
|
TransactionManager.registerScopeableFactory(BufferManager::get);
|
809 |
|
BufferManager.initialized = true;
|
810 |
|
}
|
811 |
|
|
|
811 |
|
812 |
812 |
/**
|
813 |
813 |
* Register this buffer as 'dirty' in the current scope.
|
814 |
814 |
* <p>
|
... | ... | |
821 |
821 |
*/
|
822 |
822 |
public void registerDirtyBuffer(RecordBuffer recordBuffer)
|
823 |
823 |
{
|
824 |
|
dirtyBuffers.addEntry(false, recordBuffer, false);
|
|
824 |
// although no state in BufferManager is touched when registering a dirty buffer, we need to register
|
|
825 |
// it with the current block, as there is logic dependent on the 'depth' of the application stack which
|
|
826 |
// uses buffers
|
|
827 |
registerScopeable(this);
|
|
828 |
|
|
829 |
txWrapperHelper.registerDirtyBuffer(recordBuffer);
|
825 |
830 |
}
|
826 |
831 |
|
827 |
832 |
/**
|
... | ... | |
912 |
917 |
}
|
913 |
918 |
|
914 |
919 |
/**
|
915 |
|
* A full transaction or sub-transaction is committing. Notify all dirty buffers in the current scope.
|
916 |
|
*
|
917 |
|
* @param transaction
|
918 |
|
* {@code true} if full transaction; {@code false} if sub-transaction.
|
919 |
|
*/
|
920 |
|
@Override
|
921 |
|
public void commit(boolean transaction)
|
922 |
|
{
|
923 |
|
Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
|
924 |
|
if (buffers != null && !buffers.isEmpty())
|
925 |
|
{
|
926 |
|
for (RecordBuffer buf : buffers.keySet())
|
927 |
|
{
|
928 |
|
buf.commit(transaction);
|
929 |
|
}
|
930 |
|
if (transaction)
|
931 |
|
{
|
932 |
|
buffers.clear();
|
933 |
|
}
|
934 |
|
}
|
935 |
|
}
|
936 |
|
|
937 |
|
/**
|
938 |
|
* A full transaction or sub-transaction is rolling back. Notify all dirty buffers in the current scope.
|
939 |
|
*
|
940 |
|
* @param transaction
|
941 |
|
* {@code true} if full transaction; {@code false} if sub-transaction.
|
942 |
|
*/
|
943 |
|
@Override
|
944 |
|
public void rollback(boolean transaction)
|
945 |
|
{
|
946 |
|
Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
|
947 |
|
if (buffers != null && !buffers.isEmpty())
|
948 |
|
{
|
949 |
|
for (RecordBuffer buf : buffers.keySet())
|
950 |
|
{
|
951 |
|
buf.rollback(transaction);
|
952 |
|
}
|
953 |
|
if (transaction)
|
954 |
|
{
|
955 |
|
buffers.clear();
|
956 |
|
}
|
957 |
|
}
|
958 |
|
}
|
959 |
|
|
960 |
|
/**
|
961 |
|
* A block is exiting or iterating normally within a transaction. Notify all dirty buffers in the current
|
962 |
|
* scope.
|
963 |
|
*
|
964 |
|
* @param transaction
|
965 |
|
* {@code true} if full transaction; {@code false} if sub-transaction.
|
966 |
|
* @param aggressiveFlush
|
967 |
|
* {@code true} if transaction manager is in aggressive subtransaction
|
968 |
|
* flush mode, indicating that any transient buffers should be validated and
|
969 |
|
* flushed, regardless of other state.
|
970 |
|
*/
|
971 |
|
@Override
|
972 |
|
public void validate(boolean transaction, boolean aggressiveFlush)
|
973 |
|
{
|
974 |
|
TailMap<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
|
975 |
|
if (buffers != null && !buffers.isEmpty())
|
976 |
|
{
|
977 |
|
ArrayList<Map<RecordBuffer, Boolean>> validatedAddedDirtyBuffers = null;
|
978 |
|
try (AutoCloseable itersafe = buffers.flagIterating())
|
979 |
|
{
|
980 |
|
for (RecordBuffer buf : buffers.keySet())
|
981 |
|
{
|
982 |
|
buf.validate(transaction, aggressiveFlush);
|
983 |
|
|
984 |
|
// The next block is rarely entered, only in edge cases.
|
985 |
|
// Details:
|
986 |
|
// The invocation of buf.validate can (for example) result in
|
987 |
|
// an ABL publish + subscribe (from an OEDB write trigger),
|
988 |
|
// resulting in more buffers that need to be validated.
|
989 |
|
// Since we are iterating, the extra buffers are added to
|
990 |
|
// a standalone 'tail' collection. We validate them immediately,
|
991 |
|
// and add them to dirtyBuffers after iterating has completed.
|
992 |
|
Map<RecordBuffer, Boolean> addedDirtyBuffers = buffers.pop();
|
993 |
|
while (addedDirtyBuffers != null)
|
994 |
|
{
|
995 |
|
// Validate all additions
|
996 |
|
addedDirtyBuffers.keySet().forEach((v) -> v.validate(transaction, aggressiveFlush));
|
997 |
|
|
998 |
|
// Store the validated additions to add them afterwards
|
999 |
|
// A list is used for clarity and to facilitate possible
|
1000 |
|
// removal logic in the future (which can't happen at the moment).
|
1001 |
|
if (validatedAddedDirtyBuffers == null)
|
1002 |
|
{
|
1003 |
|
validatedAddedDirtyBuffers = new ArrayList<>();
|
1004 |
|
}
|
1005 |
|
validatedAddedDirtyBuffers.add(addedDirtyBuffers);
|
1006 |
|
|
1007 |
|
// Fetch possible new additions of the added validate invocations.
|
1008 |
|
// We take control of this collection, at the TailMap it
|
1009 |
|
// is forgotten.
|
1010 |
|
addedDirtyBuffers = buffers.pop();
|
1011 |
|
}
|
1012 |
|
}
|
1013 |
|
}
|
1014 |
|
catch (Exception e)
|
1015 |
|
{
|
1016 |
|
throw new IllegalStateException("Exception at dirtyBuffers.flagIterating block", e);
|
1017 |
|
}
|
1018 |
|
|
1019 |
|
// The next block is rarely entered, only in edge cases, see above.
|
1020 |
|
if (validatedAddedDirtyBuffers != null)
|
1021 |
|
{
|
1022 |
|
for (Map<RecordBuffer, Boolean> bufmap : validatedAddedDirtyBuffers)
|
1023 |
|
{
|
1024 |
|
// Note: addEntry(false, ...) because:
|
1025 |
|
// We do not need the global scope.
|
1026 |
|
// At the top of this method, scope 0 is used, not scope -1 (global).
|
1027 |
|
bufmap.forEach((k,v) -> dirtyBuffers.addEntry(false, k, v));
|
1028 |
|
}
|
1029 |
|
}
|
1030 |
|
|
1031 |
|
// do not clear the buffers here, only commit/rollback can clear it
|
1032 |
|
}
|
1033 |
|
}
|
1034 |
|
|
1035 |
|
/**
|
1036 |
920 |
* Query the scope level which currently is open. This is the number of "important" scopes;
|
1037 |
921 |
* NO_TRANSACTION blocks inside an application transaction are ignored. So, this number may be lower
|
1038 |
922 |
* than the actual number of nested block scopes open, as tracked by the {@code TransactionManager}.
|
... | ... | |
1045 |
929 |
}
|
1046 |
930 |
|
1047 |
931 |
/**
|
|
932 |
* Find the nearest external block (from the bottom of the stack).
|
|
933 |
*
|
|
934 |
* @return See above.
|
|
935 |
*/
|
|
936 |
public int findNearestExternal()
|
|
937 |
{
|
|
938 |
int idx = externalScope.size() - 1;
|
|
939 |
while (idx > 0)
|
|
940 |
{
|
|
941 |
if (externalScope.get(idx))
|
|
942 |
{
|
|
943 |
return idx + 1;
|
|
944 |
}
|
|
945 |
|
|
946 |
idx = idx - 1;
|
|
947 |
}
|
|
948 |
|
|
949 |
return -1;
|
|
950 |
}
|
|
951 |
|
|
952 |
/**
|
1048 |
953 |
* Check the specified exception and determine whether we want to allow it
|
1049 |
954 |
* to be honored by the {@link TransactionManager}, or whether we want to
|
1050 |
955 |
* veto the STOP at this scope.
|
... | ... | |
1107 |
1012 |
*/
|
1108 |
1013 |
public void beginTx(boolean inTx, boolean fullTx, int blockDepth)
|
1109 |
1014 |
{
|
1110 |
|
if (inTx)
|
1111 |
|
{
|
1112 |
|
if (fullTx)
|
1113 |
|
{
|
1114 |
|
// create a database xaction wrapper for each connected database, but do not
|
1115 |
|
// activate it yet
|
1116 |
|
txWrapperMap.clear(); // should be empty already
|
1117 |
|
inactiveTxWrappers.clear(); // should be empty already
|
1118 |
|
List<Database> databases = connMgr.getActiveDatabases();
|
1119 |
|
for (Database database : databases)
|
1120 |
|
{
|
1121 |
|
createTxWrapper(database);
|
1122 |
|
}
|
1123 |
|
|
1124 |
|
transactionDepth = blockDepth - 1; // match TM transaction level
|
1125 |
|
transactionCount++;
|
1126 |
|
|
1127 |
|
// register for master transaction finish/iterate to clear transaction depth
|
1128 |
|
txHelper.registerTransactionFinish(xactFin, true);
|
1129 |
|
}
|
1130 |
|
|
1131 |
|
// register various services for callbacks if current block has transaction properties
|
1132 |
|
if (txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION)
|
1133 |
|
{
|
1134 |
|
// register savepoint manager for callbacks at full and sub-transaction blocks
|
1135 |
|
for (TxWrapper txw : txWrapperMap.values())
|
1136 |
|
{
|
1137 |
|
txw.registerSavepointHooks(fullTx);
|
1138 |
|
}
|
1139 |
|
|
1140 |
|
// register to get Commitable notifications for open buffers
|
1141 |
|
txHelper.registerCommitAt(blockDepth - 1, this);
|
1142 |
|
|
1143 |
|
// register unique tracker context to get Commitable/Finalizable notifications
|
1144 |
|
uniqueTrackerCtx.register(txHelper, blockDepth - 1);
|
1145 |
|
}
|
1146 |
|
}
|
1147 |
|
|
1148 |
|
scopeTransitions++;
|
1149 |
|
|
1150 |
|
if (fullTx)
|
1151 |
|
{
|
1152 |
|
// activate a pending TxWrapper for any database with a buffer that already is open
|
1153 |
|
Iterator<Database> iter = activeDatabases.keySet().iterator();
|
1154 |
|
while (iter.hasNext())
|
1155 |
|
{
|
1156 |
|
Database database = iter.next();
|
1157 |
|
maybeActivateTxWrapper(database);
|
1158 |
|
}
|
1159 |
|
}
|
|
1015 |
txWrapperHelper.beginTx(inTx, fullTx, blockDepth);
|
1160 |
1016 |
}
|
1161 |
1017 |
|
1162 |
1018 |
/**
|
... | ... | |
1169 |
1025 |
*/
|
1170 |
1026 |
public void endTx(boolean inTx, boolean fullTx)
|
1171 |
1027 |
{
|
1172 |
|
scopeTransitions++;
|
|
1028 |
txWrapperHelper.endTx(inTx, fullTx);
|
1173 |
1029 |
}
|
1174 |
1030 |
|
1175 |
1031 |
/**
|
... | ... | |
1178 |
1034 |
public void endTxPost()
|
1179 |
1035 |
{
|
1180 |
1036 |
// if any DBs were used in this tx block and remained connected, disconnect now
|
1181 |
|
connMgr.transactionEnded();
|
|
1037 |
txWrapperHelper.endTxPost();
|
1182 |
1038 |
}
|
1183 |
1039 |
|
1184 |
1040 |
/**
|
... | ... | |
1197 |
1053 |
@Override
|
1198 |
1054 |
public void scopeStart()
|
1199 |
1055 |
{
|
1200 |
|
if (!isImportantBlockTransition())
|
1201 |
|
{
|
1202 |
|
// nothing to do
|
1203 |
|
return;
|
1204 |
|
}
|
1205 |
|
|
1206 |
1056 |
BlockType bt = txHelper.getBlockType();
|
1207 |
1057 |
if (bt.equals(BlockType.INTERNAL_PROC) ||
|
1208 |
1058 |
bt.equals(BlockType.FUNCTION) ||
|
... | ... | |
1228 |
1078 |
|
1229 |
1079 |
allBuffers.addScope(null);
|
1230 |
1080 |
byLegacyName.addScope(null);
|
1231 |
|
loadedBuffers.addScope(null);
|
1232 |
|
dirtyBuffers.addScope(null);
|
1233 |
1081 |
openBuffers.pushScope();
|
1234 |
1082 |
openStaticTempTables.addScope(null);
|
1235 |
1083 |
thOutputParameters.addScope(null);
|
1236 |
|
undoData.addScope(null);
|
1237 |
|
fieldScopes.scopeStart();
|
|
1084 |
externalScope.push(txHelper.isExternalBlock());
|
1238 |
1085 |
|
1239 |
1086 |
BatchModeData crtBatchScope = batchModeStack.peek();
|
1240 |
1087 |
if (crtBatchScope != null && crtBatchScope.batchDepth == BatchModeData.INTERNAL)
|
... | ... | |
1287 |
1134 |
LOG.log(Level.FINER,
|
1288 |
1135 |
Utils.describeContext() + ": block scope " + blockDepth + " started");
|
1289 |
1136 |
}
|
1290 |
|
|
1291 |
|
boolean inTx = txHelper.isTransaction();
|
1292 |
|
boolean fullTx = inTx && txHelper.isFullTransaction();
|
1293 |
|
|
1294 |
|
beginTx(inTx, fullTx, blockDepth);
|
1295 |
1137 |
}
|
1296 |
1138 |
|
1297 |
1139 |
/**
|
... | ... | |
1301 |
1143 |
@Override
|
1302 |
1144 |
public void scopeFinished()
|
1303 |
1145 |
{
|
1304 |
|
if (!isImportantBlockTransition())
|
1305 |
|
{
|
1306 |
|
// nothing to do
|
1307 |
|
return;
|
1308 |
|
}
|
1309 |
|
|
1310 |
1146 |
PersistentProcScope ppScope = null;
|
1311 |
1147 |
|
1312 |
1148 |
if (!txHelper.isGlobalBlock())
|
... | ... | |
1327 |
1163 |
}
|
1328 |
1164 |
}
|
1329 |
1165 |
|
1330 |
|
// here we will be out of sync with the TM on a transaction boundary, because our
|
1331 |
|
// transaction depth is cleared with a transaction finalizable, which is called before this
|
1332 |
|
// scope finished notification, so we rely on the TM.isTransaction() instead of our own
|
1333 |
|
boolean inTx = txHelper.isTransaction();
|
1334 |
|
boolean fullTx = txHelper.isFullTransaction();
|
1335 |
|
|
1336 |
|
endTx(inTx, fullTx);
|
1337 |
|
|
1338 |
|
Map<RecordBuffer, Boolean> dirtyBufs = dirtyBuffers.getDictionaryAtScope(0, false);
|
1339 |
|
dirtyBuffers.deleteScope();
|
1340 |
|
if (dirtyBufs != null && !dirtyBufs.isEmpty() && dirtyBuffers.size() > 1)
|
1341 |
|
{
|
1342 |
|
// do not merge into the global block, as all changes should have been already committed or rolled
|
1343 |
|
// back. this is required as temporary buffers can be changed without an active transaction present
|
1344 |
|
// on the stack: in this case, they will be pushed to the previous scope until the global block is
|
1345 |
|
// reached, when they will be discarded.
|
1346 |
|
|
1347 |
|
for (RecordBuffer buffer : dirtyBufs.keySet())
|
1348 |
|
{
|
1349 |
|
// merge into previous scope, but only buffers with records which were changed, as the record
|
1350 |
|
// associated (at initial dirty registration of the buffer) may have been flushed to the database,
|
1351 |
|
// and the current record may not be 'dirty'
|
1352 |
|
if (buffer.getCurrentRecord() != null)
|
1353 |
|
{
|
1354 |
|
Record record = buffer.getCurrentRecord();
|
1355 |
|
if (buffer.isTouched() ||
|
1356 |
|
record.checkState(DmoState.NEW) ||
|
1357 |
|
record.checkState(DmoState.STALE) ||
|
1358 |
|
record.checkState(DmoState.DELETING) ||
|
1359 |
|
record.checkState(DmoState.DELETED) ||
|
1360 |
|
record.checkState(DmoState.INVALID) ||
|
1361 |
|
record.checkState(DmoState.CHANGED) ||
|
1362 |
|
record.checkState(DmoState.TRACKED))
|
1363 |
|
{
|
1364 |
|
dirtyBuffers.addEntry(false, buffer, false);
|
1365 |
|
}
|
1366 |
|
}
|
1367 |
|
}
|
1368 |
|
}
|
1369 |
|
undoData.deleteScope();
|
1370 |
|
|
1371 |
|
// Copy the set of buffers contained in the current scope of the
|
1372 |
|
// loadedBuffers (which is about to be removed) to the enclosing scope.
|
1373 |
|
// Only buffers which are open at the enclosing or a higher scope
|
1374 |
|
// should be copied.
|
1375 |
|
if (loadedBuffers.size() > 1)
|
1376 |
|
{
|
1377 |
|
Set<RecordBuffer> currentLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false).keySet();
|
1378 |
|
|
1379 |
|
Collection<RecordBuffer> currentOpenBuffers = openBuffers.sublistAtScope(0);
|
1380 |
|
|
1381 |
|
for (RecordBuffer buffer : currentLoadedBuffers)
|
1382 |
|
{
|
1383 |
|
if (buffer.getOpenScopeCount() > 1 || !currentOpenBuffers.contains(buffer))
|
1384 |
|
{
|
1385 |
|
// if this buffer is missing at the current scope of open
|
1386 |
|
// buffers or it presents there but has open scope count > 1,
|
1387 |
|
// that means that it presents at a higher level and it should
|
1388 |
|
// be copied to the enclosing scope
|
1389 |
|
loadedBuffers.setValueAtScope(buffer, 1, false);
|
1390 |
|
buffer.addLoadedBuffersScope(loadedBuffers.size() - 1);
|
1391 |
|
}
|
1392 |
|
}
|
1393 |
|
}
|
1394 |
1166 |
if (ppScope != null)
|
1395 |
1167 |
{
|
1396 |
1168 |
// save the data for this external procedure, as these will need to be removed from the
|
1397 |
1169 |
// global scope, when the procedure gets deleted.
|
1398 |
|
Map<RecordBuffer, Boolean> thisLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false);
|
1399 |
|
ppScope.loadedBuffers.putAll(thisLoadedBuffers);
|
1400 |
1170 |
|
1401 |
1171 |
List<RecordBuffer> thisOpenBuffers = openBuffers.sublistAtScope(0);
|
1402 |
1172 |
ppScope.openBuffers.addAll(thisOpenBuffers);
|
... | ... | |
1411 |
1181 |
ppScope.openStaticTempTables.putAll(thisOpenStaticTempTables);
|
1412 |
1182 |
|
1413 |
1183 |
// add all these to the global scope, too
|
1414 |
|
|
1415 |
|
if (!thisLoadedBuffers.isEmpty())
|
1416 |
|
{
|
1417 |
|
loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
|
1418 |
|
.putAll(thisLoadedBuffers);
|
1419 |
|
}
|
1420 |
|
|
|
1184 |
|
1421 |
1185 |
if (!thisOpenBuffers.isEmpty())
|
1422 |
1186 |
{
|
1423 |
1187 |
openBuffers.addAll(true, thisOpenBuffers);
|
... | ... | |
1448 |
1212 |
}
|
1449 |
1213 |
}
|
1450 |
1214 |
|
1451 |
|
loadedBuffers.deleteScope();
|
1452 |
1215 |
openBuffers.popScope();
|
1453 |
1216 |
allBuffers.deleteScope();
|
1454 |
1217 |
byLegacyName.deleteScope();
|
1455 |
|
fieldScopes.scopeFinished();
|
1456 |
1218 |
openStaticTempTables.deleteScope();
|
1457 |
1219 |
thOutputParameters.deleteScope();
|
|
1220 |
externalScope.pop();
|
1458 |
1221 |
BatchModeData bmd = batchModeStack.pop();
|
1459 |
1222 |
if (bmd.batchDepth == BatchModeData.INTERNAL)
|
1460 |
1223 |
{
|
... | ... | |
1467 |
1230 |
Utils.describeContext() + ": block scope " + txHelper.getNestingLevel() + " finished");
|
1468 |
1231 |
}
|
1469 |
1232 |
|
1470 |
|
if (fullTx)
|
1471 |
|
{
|
1472 |
|
endTxPost();
|
1473 |
|
}
|
1474 |
|
|
1475 |
1233 |
maybeCloseSessions();
|
1476 |
1234 |
}
|
1477 |
1235 |
|
... | ... | |
1479 |
1237 |
* Provides notification that the external procedure scope has been deleted.
|
1480 |
1238 |
* <p>
|
1481 |
1239 |
* If we are deleting a persistent procedure, clean all its data from the global scope for the
|
1482 |
|
* following dictionaries: {@link #loadedBuffers}, {@link #openBuffers}, {@link #allBuffers},
|
1483 |
|
* {@link #openStaticTempTables}.
|
|
1240 |
* following dictionaries: {@link #openBuffers}, {@link #allBuffers}, {@link #openStaticTempTables}.
|
1484 |
1241 |
*/
|
1485 |
1242 |
@Override
|
1486 |
1243 |
public void scopeDeleted()
|
... | ... | |
1502 |
1259 |
{
|
1503 |
1260 |
// remove all these from the global scope
|
1504 |
1261 |
|
1505 |
|
if (!scope.loadedBuffers.isEmpty())
|
1506 |
|
{
|
1507 |
|
loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
|
1508 |
|
.keySet()
|
1509 |
|
.removeAll(scope.loadedBuffers.keySet());
|
1510 |
|
}
|
1511 |
|
|
1512 |
1262 |
if (!scope.openBuffers.isEmpty())
|
1513 |
1263 |
{
|
1514 |
1264 |
openBuffers.removeAllFrom(true, scope.openBuffers);
|
... | ... | |
1536 |
1286 |
}
|
1537 |
1287 |
|
1538 |
1288 |
/**
|
|
1289 |
* Get the {@link ScopeId} for the instance.
|
|
1290 |
*
|
|
1291 |
* @return {@link ScopeId#BUFFER_MANAGER}.
|
|
1292 |
*/
|
|
1293 |
@Override
|
|
1294 |
public ScopeId getScopeId()
|
|
1295 |
{
|
|
1296 |
return ScopeId.BUFFER_MANAGER;
|
|
1297 |
}
|
|
1298 |
|
|
1299 |
/**
|
1539 |
1300 |
* Provides a notification that a batch is starting or ending. Enters or
|
1540 |
1301 |
* exits batch assignment mode for all record buffers in this context,
|
1541 |
1302 |
* depending on the value of <code>start</code>.
|
... | ... | |
1560 |
1321 |
}
|
1561 |
1322 |
|
1562 |
1323 |
/**
|
1563 |
|
* Releases the buffers into which a new record was loaded (or the old
|
1564 |
|
* record was reloaded) into the current or deeper scopes, or only clears
|
1565 |
|
* the list of buffers pending to be released.
|
1566 |
|
*
|
1567 |
|
* @param clearOnly
|
1568 |
|
* If <code>true</code> then clear the list of buffers pending to be
|
1569 |
|
* released, otherwise perform release of these buffers and clear
|
1570 |
|
* the list after that.
|
1571 |
|
*/
|
1572 |
|
public void resetState(boolean clearOnly)
|
1573 |
|
{
|
1574 |
|
Map<RecordBuffer, Boolean> buffers = loadedBuffers.getDictionaryAtScope(0, false);
|
1575 |
|
if (buffers == null || buffers.isEmpty())
|
1576 |
|
{
|
1577 |
|
return;
|
1578 |
|
}
|
1579 |
|
|
1580 |
|
if (!clearOnly)
|
1581 |
|
{
|
1582 |
|
for (RecordBuffer buffer : buffers.keySet())
|
1583 |
|
{
|
1584 |
|
if (!buffer.isPendingRollbackProcessed())
|
1585 |
|
{
|
1586 |
|
buffer.release(false, false);
|
1587 |
|
}
|
1588 |
|
else
|
1589 |
|
{
|
1590 |
|
buffer.resetPendingRollbackProcessed();
|
1591 |
|
}
|
1592 |
|
}
|
1593 |
|
}
|
1594 |
|
|
1595 |
|
buffers.clear();
|
1596 |
|
}
|
1597 |
|
|
1598 |
|
/**
|
1599 |
1324 |
* Marks the table for postponed delete, if it is possible. Postponed delete can take place if
|
1600 |
1325 |
* the table is used as [INPUT-]OUTPUT TABLE-HANDLE parameter.
|
1601 |
1326 |
*
|
... | ... | |
1648 |
1373 |
*/
|
1649 |
1374 |
public boolean isTransaction()
|
1650 |
1375 |
{
|
1651 |
|
return transactionDepth >= 0;
|
1652 |
|
}
|
1653 |
|
|
1654 |
|
/**
|
1655 |
|
* Check whether the block at the specified block depth is within an application-level
|
1656 |
|
* transaction.
|
1657 |
|
*
|
1658 |
|
* @param level
|
1659 |
|
* Zero-based block depth (counting from the outermost scope).
|
1660 |
|
*
|
1661 |
|
* @return {@code true} if we are in a transaction, else {@code false}.
|
1662 |
|
*/
|
1663 |
|
public boolean isTransactionAt(int level)
|
1664 |
|
{
|
1665 |
|
return transactionDepth >= 0 && transactionDepth <= level;
|
1666 |
|
}
|
1667 |
|
|
1668 |
|
/**
|
1669 |
|
* Check whether the current block marks (and is within) the boundary of an application-level
|
1670 |
|
* full transaction.
|
1671 |
|
*
|
1672 |
|
* @return {@code true} if we are at (and within) a full transaction boundary, else {@code
|
1673 |
|
* false}.
|
1674 |
|
*/
|
1675 |
|
public boolean isFullTransaction()
|
1676 |
|
{
|
1677 |
|
return transactionDepth >= 0 && getOpenBufferScopes() - 1 == transactionDepth;
|
1678 |
|
}
|
1679 |
|
|
1680 |
|
/**
|
1681 |
|
* Get the zero-based block depth (counting from the outermost scope) at which the current,
|
1682 |
|
* full, application-level transaction (if any) began.
|
1683 |
|
*
|
1684 |
|
* @return Zero-based block depth of current, full transaction, or -1 if we are not currently
|
1685 |
|
* within a transaction.
|
1686 |
|
*/
|
1687 |
|
public int getFullTransactionBlock()
|
1688 |
|
{
|
1689 |
|
return transactionDepth;
|
|
1376 |
return txWrapperHelper.isTransaction();
|
1690 |
1377 |
}
|
1691 |
1378 |
|
1692 |
1379 |
/**
|
... | ... | |
1699 |
1386 |
*/
|
1700 |
1387 |
public long getScopeTransitions()
|
1701 |
1388 |
{
|
1702 |
|
return scopeTransitions;
|
|
1389 |
return txWrapperHelper.getScopeTransitions();
|
|
1390 |
}
|
|
1391 |
|
|
1392 |
/**
|
|
1393 |
* Get the open buffers from the current scope.
|
|
1394 |
*
|
|
1395 |
* @return See above.
|
|
1396 |
*/
|
|
1397 |
Collection<RecordBuffer> getOpenBuffers()
|
|
1398 |
{
|
|
1399 |
return openBuffers.scopes() == 0 ? Collections.EMPTY_LIST : openBuffers.sublistAtScope(0);
|
1703 |
1400 |
}
|
1704 |
1401 |
|
1705 |
1402 |
/**
|
... | ... | |
1709 |
1406 |
*/
|
1710 |
1407 |
long getTransactionCount()
|
1711 |
1408 |
{
|
1712 |
|
return transactionCount;
|
|
1409 |
return txWrapperHelper.getTransactionCount();
|
1713 |
1410 |
}
|
1714 |
1411 |
|
1715 |
1412 |
/**
|
... | ... | |
1752 |
1449 |
throw new UnsupportedOperationException(
|
1753 |
1450 |
"A buffer can't be bound to more than one table in the same program!");
|
1754 |
1451 |
}
|
|
1452 |
|
1755 |
1453 |
dstBuffers.add(dstBuf);
|
1756 |
1454 |
|
1757 |
1455 |
bindBuffer(dstBuf, srcBuf);
|
... | ... | |
1870 |
1568 |
}
|
1871 |
1569 |
|
1872 |
1570 |
/**
|
1873 |
|
* Register an undoable which is responsible for setting the buffer's
|
1874 |
|
* proper current record after all reversibles are processed.
|
1875 |
|
*
|
1876 |
|
* @param buffer
|
1877 |
|
* Record buffer for which a change is being tracked for undo
|
1878 |
|
* purposes. It is assumed the buffer has been initialized.
|
1879 |
|
* @param undoTarget
|
1880 |
|
* The undoable responsible with setting the buffer back to its
|
1881 |
|
* proper record.
|
1882 |
|
*/
|
1883 |
|
void registerBufferUndoable(RecordBuffer buffer, Undoable undoTarget)
|
1884 |
|
{
|
1885 |
|
Map<BufferType, UndoData> dict = undoData.getDictionaryAtScope(0, true);
|
1886 |
|
|
1887 |
|
RecordBuffer master = buffer.getMasterBuffer();
|
1888 |
|
BufferType key = master.getBufferType();
|
1889 |
|
|
1890 |
|
if (dict != null)
|
1891 |
|
{
|
1892 |
|
UndoData ud = dict.get(key);
|
1893 |
|
if (ud == null)
|
1894 |
|
{
|
1895 |
|
ud = new UndoData();
|
1896 |
|
dict.put(key, ud);
|
1897 |
|
}
|
1898 |
|
|
1899 |
|
ud.bufferUndoable = undoTarget.deepCopy();
|
1900 |
|
}
|
1901 |
|
}
|
1902 |
|
|
1903 |
|
/**
|
1904 |
1571 |
* Register a newly defined record buffer in the current scope. Buffers are deregistered
|
1905 |
1572 |
* implicitly when the scope in which they were registered ends.
|
1906 |
1573 |
*
|
... | ... | |
1937 |
1604 |
*/
|
1938 |
1605 |
void registerPending(Class<?> def, RecordBuffer buffer)
|
1939 |
1606 |
{
|
|
1607 |
registerPendingScopeable(this);
|
|
1608 |
|
1940 |
1609 |
pendingBuffers.add(buffer);
|
1941 |
1610 |
|
1942 |
1611 |
registerPendingBufferClass(buffer, def);
|
... | ... | |
1983 |
1652 |
|
1984 |
1653 |
int[] allBufferScopes = buffer.getAllBuferScopes();
|
1985 |
1654 |
int[] byLegacyNameScopes = buffer.getByLegacyNameScopes();
|
1986 |
|
int[] loadedBuffersScopes = buffer.getLoadedBuffersScope();
|
1987 |
1655 |
int[] openBuffersScopes = buffer.getOpenBuffersScope();
|
1988 |
1656 |
|
1989 |
1657 |
int scopeSize = allBuffers.size();
|
... | ... | |
2029 |
1697 |
// remove from global scope always
|
2030 |
1698 |
// openBuffers.removeFrom(openScopes - 1, buffer);
|
2031 |
1699 |
|
2032 |
|
int loadedScopes = loadedBuffers.size();
|
2033 |
|
for (int scope : loadedBuffersScopes)
|
2034 |
|
{
|
2035 |
|
int depth = loadedScopes - scope;
|
2036 |
|
Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(depth, false);
|
2037 |
|
if (loaded != null)
|
2038 |
|
{
|
2039 |
|
loaded.remove(buffer);
|
2040 |
|
}
|
2041 |
|
}
|
2042 |
|
// remove from global scope always
|
2043 |
|
Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(-1, false);
|
2044 |
|
if (loaded != null)
|
2045 |
|
{
|
2046 |
|
loaded.remove(buffer);
|
2047 |
|
}
|
|
1700 |
txWrapperHelper.deregisterDynamicBuffer(buffer);
|
2048 |
1701 |
|
2049 |
1702 |
if (referent != null)
|
2050 |
1703 |
{
|
... | ... | |
2053 |
1706 |
{
|
2054 |
1707 |
procScope.allBuffers.remove(key);
|
2055 |
1708 |
byLegacy.accept(procScope.byLegacyName);
|
2056 |
|
procScope.loadedBuffers.remove(buffer);
|
2057 |
1709 |
procScope.openBuffers.remove(buffer);
|
2058 |
1710 |
}
|
2059 |
1711 |
}
|
... | ... | |
2082 |
1734 |
*/
|
2083 |
1735 |
void startBatchAssignMode(boolean internal)
|
2084 |
1736 |
{
|
|
1737 |
registerScopeable(this);
|
|
1738 |
|
2085 |
1739 |
int currentScope = getOpenBufferScopes();
|
2086 |
1740 |
BatchModeData bmd = batchModeStack.peek();
|
2087 |
1741 |
|
... | ... | |
2382 |
2036 |
}
|
2383 |
2037 |
|
2384 |
2038 |
/**
|
2385 |
|
* Remove all <code>Reversible</code> objects associated with the given buffer from their
|
2386 |
|
* respective scoped dictionaries.
|
2387 |
|
*
|
2388 |
|
* @param buffer
|
2389 |
|
* Buffer associated with the <code>Reversible</code>s to be removed.
|
2390 |
|
*/
|
2391 |
|
void removeAllReversibles(TemporaryBuffer buffer)
|
2392 |
|
{
|
2393 |
|
// delete entries from current and parent scopes
|
2394 |
|
BufferType bufType = buffer.getMasterBuffer().getBufferType();
|
2395 |
|
undoData.removeEntryThroughScope(bufType, 1);
|
2396 |
|
}
|
2397 |
|
|
2398 |
|
/**
|
2399 |
2039 |
* Called by {@link RecordBuffer} when a new buffer scope is opened. It registers the open scope with the
|
2400 |
2040 |
* buffer manager, so that the buffer can receive block scope start and stop notifications, and be
|
2401 |
2041 |
* registered for commit/rollback/validate processing when a full transaction is begun.
|
... | ... | |
2457 |
2097 |
*/
|
2458 |
2098 |
void registerPendingStaticTempTable(StaticTempTable table, boolean global)
|
2459 |
2099 |
{
|
|
2100 |
registerPendingScopeable(this);
|
|
2101 |
|
2460 |
2102 |
pendingStaticTempTables.put(table, global);
|
2461 |
2103 |
}
|
2462 |
2104 |
|
... | ... | |
2658 |
2300 |
*/
|
2659 |
2301 |
void openScopeAt(int openScopeDepth, RecordBuffer buffer)
|
2660 |
2302 |
{
|
|
2303 |
// TODO: this seems to be called only for 'openScopeDepth == 0'.
|
|
2304 |
|
2661 |
2305 |
if (buffer.isActive())
|
2662 |
2306 |
{
|
2663 |
2307 |
if (openScopeDepth == 0)
|
... | ... | |
2759 |
2403 |
*/
|
2760 |
2404 |
void maybeActivateTxWrapper(Database database)
|
2761 |
2405 |
{
|
2762 |
|
TxWrapper txw = inactiveTxWrappers.remove(database);
|
2763 |
|
if (txw != null)
|
2764 |
|
{
|
2765 |
|
txw.activate();
|
2766 |
|
}
|
|
2406 |
txWrapperHelper.maybeActivateTxWrapper(database);
|
2767 |
2407 |
}
|
2768 |
2408 |
|
2769 |
2409 |
/**
|
... | ... | |
2932 |
2572 |
*/
|
2933 |
2573 |
void notifyRecordWasLoaded(RecordBuffer recordBuffer)
|
2934 |
2574 |
{
|
2935 |
|
if (recordBuffer.getCurrentRecord() != null && !recordBuffer.isDynamic())
|
2936 |
|
{
|
2937 |
|
pm.notifyStaticRecordWasLoaded(recordBuffer);
|
2938 |
|
}
|
2939 |
|
|
2940 |
|
if (recordBuffer.getCurrentRecord() == null || recordBuffer.isTemporary())
|
2941 |
|
{
|
2942 |
|
return;
|
2943 |
|
}
|
2944 |
|
|
2945 |
|
loadedBuffers.addEntry(false, recordBuffer, false);
|
2946 |
|
recordBuffer.addLoadedBuffersScope(loadedBuffers.size());
|
|
2575 |
txWrapperHelper.notifyRecordWasLoaded(recordBuffer);
|
2947 |
2576 |
}
|
2948 |
2577 |
|
2949 |
2578 |
/**
|
... | ... | |
3035 |
2664 |
}
|
3036 |
2665 |
|
3037 |
2666 |
/**
|
3038 |
|
* Create an inactive transaction wrapper for the given database. It will be activated the
|
3039 |
|
* first time an open buffer scope is detected or when a new buffer scope is opened, at which
|
3040 |
|
* time a database-level transaction will be opened.
|
3041 |
|
*
|
3042 |
|
* @param database
|
3043 |
|
* Database for which this transaction wrapper is created.
|
3044 |
|
*
|
3045 |
|
* @return Transaction wrapper object.
|
3046 |
|
*/
|
3047 |
|
private TxWrapper createTxWrapper(Database database)
|
3048 |
|
{
|
3049 |
|
TxWrapper txw = new TxWrapper(database);
|
3050 |
|
txWrapperMap.put(database, txw);
|
3051 |
|
inactiveTxWrappers.put(database, txw);
|
3052 |
|
|
3053 |
|
return txw;
|
3054 |
|
}
|
3055 |
|
|
3056 |
|
/**
|
3057 |
|
* Compose a message suitable for logging. Reports open buffer scopes and
|
3058 |
|
* context information in addition to a custom message.
|
3059 |
|
*
|
3060 |
|
* @param persistence
|
3061 |
|
* Persistence object which will provide context info.
|
3062 |
|
* @param text
|
3063 |
|
* Custom message text.
|
3064 |
|
*
|
3065 |
|
* @return Composed message text.
|
3066 |
|
*/
|
3067 |
|
private String message(Persistence persistence, String text)
|
3068 |
|
{
|
3069 |
|
return persistence.message("scope " + getOpenBufferScopes() + ": " + text);
|
3070 |
|
}
|
3071 |
|
|
3072 |
|
/**
|
3073 |
2667 |
* Add the source buffers to the destination map.
|
3074 |
2668 |
*
|
3075 |
2669 |
* @param dst
|
... | ... | |
3103 |
2697 |
*/
|
3104 |
2698 |
private void trackDatabase(RecordBuffer buffer, boolean add)
|
3105 |
2699 |
{
|
3106 |
|
if (add)
|
3107 |
|
{
|
3108 |
|
Database database = buffer.getDatabase();
|
3109 |
|
MutableInteger count = activeDatabases.get(database);
|
3110 |
|
if (count == null)
|
3111 |
|
{
|
3112 |
|
activeDatabases.put(database, count = new MutableInteger(0));
|
3113 |
|
}
|
3114 |
|
count.set(count.get() + 1);
|
3115 |
|
}
|
3116 |
|
else if (buffer.isActive() && buffer.getOpenScopeCount() > 0)
|
3117 |
|
{
|
3118 |
|
Database database = buffer.getDatabase();
|
3119 |
|
MutableInteger count = activeDatabases.get(database);
|
3120 |
|
if (count == null)
|
3121 |
|
{
|
3122 |
|
throw new IllegalStateException("Tracking for " + database + " is unbalanced!");
|
3123 |
|
}
|
3124 |
|
|
3125 |
|
if (count.get() == 1)
|
3126 |
|
{
|
3127 |
|
activeDatabases.remove(database);
|
3128 |
|
}
|
3129 |
|
else
|
3130 |
|
{
|
3131 |
|
count.set(count.get() - 1);
|
3132 |
|
}
|
3133 |
|
}
|
|
2700 |
txWrapperHelper.trackDatabase(buffer, add);
|
3134 |
2701 |
}
|
3135 |
2702 |
|
3136 |
2703 |
/**
|
... | ... | |
3147 |
2714 |
* @return {@code true} if the block is considered "important" by the above criteria, else
|
3148 |
2715 |
* {@code false}.
|
3149 |
2716 |
*/
|
3150 |
|
private boolean isImportantBlockTransition()
|
3151 |
|
{
|
3152 |
|
return !txHelper.isTransaction() ||
|
3153 |
|
txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION;
|
3154 |
|
}
|
3155 |
|
|
3156 |
|
/**
|
3157 |
|
* Storage mechanism for maps of reversible/undoable actions, keyed by
|
3158 |
|
* primary key. One instance of this class holds all such objects for a
|
3159 |
|
* single buffer in a single scope.
|
3160 |
|
*/
|
3161 |
|
private static class UndoData
|
3162 |
|
{
|
3163 |
|
/** Undoable which will set a no-undo buffer to its proper record after a rollback. */
|
3164 |
|
Undoable bufferUndoable;
|
3165 |
|
}
|
3166 |
|
|
3167 |
|
/**
|
3168 |
|
* An object which manages the "master" {@code Commitable} and {@code Finalizable} events of an
|
3169 |
|
* application transaction. It creates a database transaction to correspond with the application
|
3170 |
|
* transaction, and as it receives events from the {@code TransactionManager}, it delegates related
|
3171 |
|
* responsibilities to various components of the persistence framework.
|
3172 |
|
*/
|
3173 |
|
private class TxWrapper
|
3174 |
|
implements Finalizable,
|
3175 |
|
Commitable
|
3176 |
|
{
|
3177 |
|
/** Backing database */
|
3178 |
|
private final Database database;
|
3179 |
|
|
3180 |
|
/** Dirty share context */
|
3181 |
|
private final DirtyShareContext dirtyContext;
|
3182 |
|
|
3183 |
|
/** Savepoint manager (can be {@code null} */
|
3184 |
|
private final SavepointManager savepointManager;
|
3185 |
|
|
3186 |
|
/** Persistence context, for access to database session and transaction */
|
3187 |
|
private Persistence.Context persistenceCtx;
|
3188 |
|
|
3189 |
|
/** Roll back the transaction when block finishes, if not previously acted upon */
|
3190 |
|
private boolean autoRollback = false;
|
3191 |
|
|
3192 |
|
/** {@code true} for an initial transaction only until the first commit or rollback */
|
3193 |
|
private boolean initialTx = false;
|
3194 |
|
|
3195 |
|
/**
|
3196 |
|
* The sole constructor.
|
3197 |
|
*
|
3198 |
|
* @param database
|
3199 |
|
* The {@code Database} where the transaction takes place.
|
3200 |
|
*/
|
3201 |
|
TxWrapper(Database database)
|
3202 |
|
{
|
3203 |
|
this.database = database;
|
3204 |
|
// Because the BufferManager manages all record buffers within the current user context, we cannot
|
3205 |
|
// separate the DIRTY-READ from the normal tables at this moment. Since this instance is only used
|
3206 |
|
// for final cleanup, we acquire the context for the DIRTY-READ database so that context will be
|
3207 |
|
// cleaned up correctly. The non-DIRTY-READ information will be ignored.
|
3208 |
|
this.dirtyContext = DirtyShareFactory.getContextInstance(database);
|
3209 |
|
this.savepointManager =
|
3210 |
|
(DatabaseManager.TEMP_TABLE_DB.equals((database)) && DatabaseManager.FORCE_NO_UNDO_TEMP_TABLES)
|
3211 |
|
? null
|
3212 |
|
: new SavepointManager();
|
3213 |
|
|
3214 |
|
// register for master transaction commit/rollback
|
3215 |
|
txHelper.registerTransactionCommit(this, true);
|
3216 |
|
|
3217 |
|
// register for master transaction finish/iterate
|
3218 |
|
txHelper.registerTransactionFinish(this, true);
|
3219 |
|
}
|
3220 |
|
|
3221 |
|
/**
|
3222 |
|
* Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
|
3223 |
|
* in which the object is registered is about to iterate and attempt another pass. This provides an
|
3224 |
|
* opportunity to preserve state from the previous pass and make preparations for the coming pass.
|
3225 |
|
*/
|
3226 |
|
@Override
|
3227 |
|
public void iterate()
|
3228 |
|
{
|
3229 |
|
if (!isActive())
|
3230 |
|
{
|
3231 |
|
return;
|
3232 |
|
}
|
3233 |
|
|
3234 |
|
if (LOG.isLoggable(Level.FINE))
|
3235 |
|
{
|
3236 |
|
LOG.log(Level.FINE, message(persistenceCtx.getPersistence(), "master transaction iterate"));
|
3237 |
|
}
|
3238 |
|
|
3239 |
|
end();
|
3240 |
|
begin();
|
3241 |
|
}
|
3242 |
|
|
3243 |
|
/**
|
3244 |
|
* Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
|
3245 |
|
* in which the object is registered is about to retry the same iteration (all loop control data is
|
3246 |
|
* unchanged). This provides an opportunity to clear or reset state from the previous pass.
|
3247 |
|
*/
|
3248 |
|
@Override
|
3249 |
|
public void retry()
|
3250 |
|
{
|
3251 |
|
if (!isActive())
|
3252 |
|
{
|
3253 |
|
return;
|
3254 |
|
}
|
3255 |
|
|
3256 |
|
if (LOG.isLoggable(Level.FINE))
|
3257 |
|
{
|
3258 |
|
LOG.log(Level.FINE,
|
3259 |
|
message(persistenceCtx.getPersistence(), "master transaction retry"));
|
3260 |
|
}
|
3261 |
|
|
3262 |
|
end();
|
3263 |
|
begin();
|
3264 |
|
}
|
3265 |
|
|
3266 |
|
/**
|
3267 |
|
* Implementation of {@code Finalizable} interface: Provides a notification that the scope in which the
|
3268 |
|
* object is registered is ending and the object's reference may or may not be lost after this method
|
3269 |
|
* is called. This provides a natural and standard mechanism to clean-up any resources that may need
|
3270 |
|
* to be closed explicitly such as operating system files or sockets.
|
3271 |
|
*/
|
3272 |
|
@Override
|
3273 |
|
public void finished()
|
3274 |
|
{
|
3275 |
|
try
|
3276 |
|
{
|
3277 |
|
if (autoRollback)
|
3278 |
|
{
|
3279 |
|
if (isActive() && LOG.isLoggable(Level.FINE))
|
3280 |
|
{
|
3281 |
|
LOG.log(Level.FINE,
|
3282 |
|
message(persistenceCtx.getPersistence(), "triggering auto-rollback"));
|
3283 |
|
}
|
3284 |
|
|
3285 |
|
rollback(true);
|
3286 |
|
}
|
3287 |
|
}
|
3288 |
|
finally
|
3289 |
|
{
|
3290 |
|
if (isActive() && LOG.isLoggable(Level.FINE))
|
3291 |
|
{
|
3292 |
|
LOG.log(Level.FINE,
|
3293 |
|
message(persistenceCtx.getPersistence(), "master transaction finished"));
|
3294 |
|
}
|
3295 |
|
|
3296 |
|
txWrapperMap.remove(database);
|
3297 |
|
|
3298 |
|
if (!isActive())
|
3299 |
|
{
|
3300 |
|
inactiveTxWrappers.remove(database);
|
3301 |
|
|
3302 |
|
return;
|
3303 |
|
}
|
3304 |
|
|
3305 |
|
end();
|
3306 |
|
}
|
3307 |
|
}
|
3308 |
|
|
3309 |
|
/**
|
3310 |
|
* Implementation of {@code Finalizable} interface: provides a notification that the external program
|
3311 |
|
* in which the object is registered is being deleted and the object's reference may be lost after this
|
3312 |
|
* method is called.
|
3313 |
|
*/
|
3314 |
|
@Override
|
3315 |
|
public void deleted()
|
3316 |
|
{
|
3317 |
|
// no-op
|
3318 |
|
}
|
3319 |
|
|
3320 |
|
/**
|
3321 |
|
* Implementation of {@code Commitable} interface: saves any temporary state to the persistent form or
|
3322 |
|
* storage that is associated with this class.
|
3323 |
|
*
|
3324 |
|
* @param transaction
|
3325 |
|
* {@code true} if this is a full transaction and {@code false} if this is only a
|
3326 |
|
* sub-transaction (a nested scope with transaction support).
|
3327 |
|
*/
|
3328 |
|
@Override
|
3329 |
|
public void commit(boolean transaction)
|
3330 |
|
{
|
3331 |
|
if (!isActive())
|
3332 |
|
{
|
3333 |
|
return;
|
3334 |
|
}
|
3335 |
|
|
3336 |
|
if (LOG.isLoggable(Level.FINE))
|
3337 |
|
{
|
3338 |
|
LOG.log(Level.FINE,
|
3339 |
|
message(persistenceCtx.getPersistence(), "master transaction commit"));
|
3340 |
|
}
|
3341 |
|
|
3342 |
|
try
|
3343 |
|
{
|
3344 |
|
initialTx = false;
|
3345 |
|
autoRollback = false;
|
3346 |
|
persistenceCtx.commit();
|
3347 |
|
}
|
3348 |
|
catch (PersistenceException exc)
|
3349 |
|
{
|
3350 |
|
if (LOG.isLoggable(Level.SEVERE))
|
3351 |
|
{
|
3352 |
|
LOG.log(Level.SEVERE,
|
3353 |
|
message(persistenceCtx.getPersistence(), "commit error"), exc);
|
3354 |
|
}
|
3355 |
|
|
3356 |
|
// No good way to recover from this. Let it abend.
|
3357 |
|
throw new RuntimeException(exc);
|
3358 |
|
}
|
3359 |
|
finally
|
3360 |
|
{
|
3361 |
|
// Reclaim any primary keys which had been waiting to be recycled,
|
3362 |
|
// pending a commit.
|
3363 |
|
if (DatabaseManager.TEMP_TABLE_DB.equals(database))
|
3364 |
|
{
|
3365 |
|
Commitable ctx = TemporaryBuffer.getContextCommitable();
|
3366 |
|
ctx.commit(transaction);
|
3367 |
|
}
|
3368 |
|
}
|
3369 |
|
}
|
3370 |
|
|
3371 |
|
/**
|
3372 |
|
* Implementation of {@code Commitable} interface: notifies that a rollback has just occurred (all state
|
3373 |
|
* maintained through the {@link Undoable} interface has been already rolled back). This notification
|
3374 |
|
* can be used to implement cleanup of any custom state that needs to be maintained in this case. It can
|
3375 |
|
* also be used to implement a rollback that is completely independent of the {@code Undoable}
|
3376 |
|
* (externally driven) interface.
|
3377 |
|
*
|
3378 |
|
* @param transaction
|
3379 |
|
* {@code true} if this is a full transaction and {@code false} if this is only a
|
3380 |
|
* sub-transaction (a nested scope with transaction support).
|
3381 |
|
*/
|
3382 |
|
@Override
|
3383 |
|
public void rollback(boolean transaction)
|
3384 |
|
{
|
3385 |
|
if (!isActive())
|
3386 |
|
{
|