# TemporaryBuffer.java

    /*
    ** Module   : TemporaryBuffer.java
    ** Abstract : Specialized record buffer which supports temp table semantics
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 ECF 20060125   @24356 Created initial version. Extends RecordBuffer
    **                           to provide temp table semantics.
    ** 002 ECF 20060414   @25545 Added clear() method. Removes all records
    **                           from a virtual temp table.
    ** 003 ECF 20060504   @26008 Added runtime support for the creation and
    **                           dropping of secondary temp tables. These are
    **                           tables created to support the extent fields
    **                           of a primary temp table.
    ** 004 GES 20060517   @26201 Match new Finalizable interface requirement.
    ** 005 ECF 20060608   @27002 Use updated signature for Persistence.list().
    ** 006 ECF 20060621   @27508 Fixed temp table cleanup. Ensure that
    **                           dropping a table or removing records as part
    **                           of multiplex scope closure is performed
    **                           within a committed database transaction. Also
    **                           added a per-context reference count facility
    **                           for temp tables.
    ** 007 ECF 20060627   @27594 Fixed temp table creation and buffer scope
    **                           opening. Temp table creation must occur
    **                           within a transaction. The superclass'
    **                           implementation of openScope() is invoked from
    **                           the overriding method.
    ** 008 ECF 20060713   @28047 Use new DBUtils class.
    ** 009 ECF 20060719   @28103 Use updated signature for Persistence.list().
    ** 010 ECF 20060914   @29596 Implemented master/slave buffers. These are
    **                           linked to one another by a common multiplex
    **                           ID.
    ** 011 ECF 20060919   @29699 Override the parent's implementations of
    **                           processChange(), incrementDMOUseCount(),
    **                           decrementDMOUseCount(), commit(), and
    **                           rollback(). The first three disable the
    **                           parent class' aggressive record eviction
    **                           policy for temp tables, and the latter two
    **                           disable subtransaction-level undo support for
    **                           temp tables. NOTE: some level of undo support
    **                           may need to be implemented eventually.
    ** 012 ECF 20061010   @30312 Added support for undoable temp tables. By
    **                           default, temp tables are not undoable,
    **                           however, this setting can be overridden by
    **                           using a new variant of the static define()
    **                           method.
    ** 013 ECF 20061019   @30545 Fixed NPE in copyAllRows(). Must not iterate
    **                           if no source records are returned by the list
    **                           query.
    ** 014 ECF 20061116   @31238 Fixed shared buffers and slave buffers.
    **                           Tables were not being cleaned up properly in
    **                           certain situations.
    ** 015 ECF 20061121   @31346 Fixed commit problem. Force ChangeBroker to
    **                           report a pending flush after persisting or
    **                           deleting records.
    ** 016 ECF 20061211   @31657 Make temp-tables undoable by default. The
    **                           no-undo variant requires a separate option to
    **                           be declared in Progress and will convert to
    **                           special variant of the define() method, so
    **                           undoability is the more appropriate default.
    ** 017 ECF 20070130   @32043 Various cleanup. Introduced methods for table
    **                           counting by context. Fixed invocations of
    **                           superclass methods to use new signatures.
    ** 018 ECF 20070202   @32072 Fixed regression in copyAllRows(). Recent
    **                           changes to bulk copy implementation had
    **                           broken this implementation.
    ** 019 ECF 20070326   @32612 Fixed associate() method and improved
    **                           removeRecords() implementation. The former
    **                           required a conditional flush before copying
    **                           input records. The latter now uses a bulk
    **                           delete instead of a looping delete.
    ** 020 ECF 20070329   @32649 Fixed shared temp-table support. Added static
    **                           method useShared() to access a previously
    **                           established, shared temp-table definition.
    ** 021 ECF 20070412   @32922 Override parent's setPinnedLockType() method.
    **                           Does nothing, since locking is a no-op for
    **                           temp tables.
    ** 022 ECF 20070415   @33012 Fixed define() variant which creates a slave
    **                           buffer. In some cases, master buffer was not
    **                           being determined correctly, resulting in an
    **                           NPE.
    ** 023 ECF 20070416   @33030 Integrated user interrupt handling.
    ** 024 ECF 20070508   @33463 Fixed associate() to properly handle a
    **                           validation exception. New requirement based
    **                           on a change to RecordBuffer.flush().
    ** 025 ECF 20070601   @33919 Fixed memory leak. Buffers were not being
    **                           removed from context's domains map when the
    **                           buffer was no longer needed, which pinned the
    **                           instances in context local memory. Changed
    **                           implementation of domains map to be a weak
    **                           hash map.
    ** 026 ECF 20070602   @33927 Implemented deferred table drop optimization.
    **                           Temp tables created for the current database
    **                           session are not dropped until the session is
    **                           being closed. This prevents a condition where
    **                           the database repeatedly creates and drops the
    **                           same tables as a secondary program which
    **                           creates TemporaryBuffers and allows them to
    **                           go out of scope is invoked within a loop of
    **                           another program.
    ** 027 EVL 20070609   @34005 Adding explicit import of the class
    **                           org.hibernate.type.Type to eliminate conflict
    **                           with the same class from java.lang.reflect
    **                           package to be able to compile for Java 6.
    ** 028 ECF 20070706   @34405 Added deregisteredSessionListener() method.
    **                           Required by change to SessionListener
    **                           interface. Also replaced StringBuffer with
    **                           StringBuilder.
    ** 029 ECF 20070715   @34856 Fixed removeRecords(). Release current buffer
    **                           record after bulk delete.
    ** 030 ECF 20070827   @34994 Added bulk delete and optimized multiplexing.
    **                           Override parent's implementation of delete()
    **                           variant which accepts a where clause and
    **                           query substitution parms to perform a bulk
    **                           delete. Omit multiplex ID from where clauses
    **                           when possible.
    ** 031 ECF 20070914   @35071 Fixed regression introduced by H030 (@34994).
    **                           When removing records during the closure of a
    **                           multiplex scope, we have to ensure only those
    **                           records associated with the multiplex ID of
    **                           the closing scope are removed from the temp
    **                           table.
    ** 032 ECF 20070914   @35294 Fixed ConnectionManager notifications. We
    **                           override the parent's implementation of two
    **                           protected methods which notify the connection
    **                           manager of buffer open and close events to do
    **                           nothing, because we don't want to trace these
    **                           for temp tables. Replaced database name
    **                           string with database information object.
    ** 033 ECF 20071002   @35332 Exposed certain methods as public. Expanded
    **                           use of generics.
    ** 034 ECF 20071018   @35592 Integrated PersistenceFactory.
    ** 035 ECF 20071108   @35909 Made nextMPID access threadsafe.
    ** 036 ECF 20071130   @36126 Integrated generics.
    ** 037 ECF 20071220   @36548 Modified Context.sessionClosing(). We now do
    **                           more state cleanup when the session is
    **                           closing due to an error.
    ** 038 SVL 20071231   @36628 Next primary key generation is carried out
    **                           to the separate function which overrides the
    **                           one in RecordBuffer.
    ** 039 ECF 20080102   @36638 Replaced use of deprecated nextPrimaryKey()
    **                           method in Persistence. Primary key generation
    **                           is now managed within Context inner class.
    ** 040 ECF 20080103   @36696 Fixed regression caused by #039 (@36638).
    **                           Used wrong buffer to call nextPrimaryKey()
    **                           from copyAllRows().
    ** 041 ECF 20080108   @36732 Fixed bulk delete. Changed call to delete()
    **                           method in Persistence.
    ** 042 ECF 20080310   @37487 Fixed potential memory leak. copyAllRows()
    **                           could leave DMOs in the Hibernate session if
    **                           they were never loaded into a buffer. Also
    **                           integrated DDL to drop indexes and to sweep
    **                           index names in create and drop statements.
    ** 043 CA  20080430   @38161 Changes due to the TempTableHelper.sql*Drop
    **                           /Create method signature change: instead of
    **                           a Persistence object, the first parameter is
    **                           a Database object.
    ** 044 SVL 20080516   @38292 Some superclass methods were overridden to
    **                           no-ops to reduce memory consumption.
    ** 045 CA  20080530   @38458 Temporary tables passed as parameters to
    **                           external procedures must match using each
    **                           field's position and type, not its name;
    **                           PROGRESS will not care if field names are
    **                           different. Ref: #38457
    ** 046 ECF 20080602   @38615 Override getIndexedProperties(boolean) to do
    **                           nothing. This method supports dirty checking,
    **                           which is not needed for temp tables.
    ** 047 ECF 20080609   @38654 Backed out #046 (@38615) which regressed
    **                           unique constraint validation. Removed
    **                           getIndexProperties(boolean) method and added
    **                           postValidateDirtyShare(), which overrides the
    **                           parent's method to do nothing.
    ** 048 CA  20080605   @38570 The multiplex ID must start from 1.
    **                           Implemented reversible bulk delete action
    **                           for temporary buffers.
    ** 049 ECF 20080708   @39075 Fixed copyAllRows(). When copying from one
    **                           table type to another, properties which are
    **                           not available via a DMO's API must be
    **                           skipped (i.e., computed columns).
    ** 050 ECF 20080723   @39172 Fixed flushing behavior for temp-tables with
    **                           no indexes. Updates to such tables must be
    **                           flushed to the database immediately.
    ** 051 ECF 20080806   @39329 Added rowid support. New primary keys are
    **                           64-bit integers.
    ** 052 CA  20080808   @39360 For no-undo tables, the bulk delete will
    **                           always force removal of the records. Also,
    **                           a reversible is registered with so the delete
    **                           will be re-executed if db tx is rolled back.
    ** 053 ECF 20080815   @39558 Support global DMO change events.
    ** 054 CA  20080815   @39468 Support API change in DBUtils.
    ** 055 SIY 20080904   @39720 Inferred generics in buffer creation methods.
    ** 056 ECF 20080925   @39950 Refactored Reversible concrete classes.
    ** 057 ECF 20080926   @39954 Fixed ReversibleBulkDelete.setDMO(). Should
    **                           not throw UnsupportedOperationException.
    ** 058 ECF 20081009   @40083 Integrated HQLPreprocessor API change.
    ** 059 ECF 20081022   @40212 Modified to adjust for Reversible API change.
    ** 060 ECF 20081127   @40710 Implement reclamation of temp table primary
    **                           keys. On delete, a record's primary key is
    **                           collected for possible recycling. On full
    **                           transaction commit/rollback, pending keys are
    **                           made available for re-use, or discarded,
    **                           respectively. Currently, keys surrendered by
    **                           bulk deletes and by rolled back record
    **                           creations are not reclaimed.
    ** 061 ECF 20081128   @40718 Fixed regression introduced by #060 (@40710).
    **                           Data structures for recycling primary keys
    **                           were not being managed by table properly.
    ** 062 ECF 20081201   @40734 Modified primary key reclamation strategy.
    **                           We now piggyback off the implementation in
    **                           the superclass, so that surrendered keys are
    **                           collected during the buffer's commit() and
    **                           rollback() operations. This fixes yet another
    **                           regression caused by the previous two mods.
    ** 063 ECF 20081202   @40744 Fixed regression in key reclamation. Must
    **                           clear key reclamation data structures when
    **                           the session is closed to avoid unique
    **                           constraint violations in following session.
    ** 064 ECF 20081211   @40871 Improved logging in temp table creation code.
    ** 065 ECF 20090107   @41025 Fixed record release during bulk delete. A
    **                           bulk delete event is now broadcast, which
    **                           allows listening buffers to release their
    **                           current records if they were deleted.
    ** 066 ECF 20090112   @41164 Fixed undo-related locking behavior. Locks
    **                           were being downgraded rather than released in
    **                           certain transaction-boundary cases. The
    **                           change necessary in this class was to
    **                           override certain lock-related methods from
    **                           the superclass to provide no-op behavior.
    ** 067 ECF 20090218   @41334 Added safety code for dealing with maps of
    **                           Reversibles. BufferManager methods which
    **                           return these maps can now return null.
    ** 068 ECF 20090227   @41446 Attempted to address memory leak. Remove all
    **                           Reversibles from BufferManager when the
    **                           associated virtual table is no longer in use.
    ** 069 GES 20090424   @41943 Import change.
    ** 070 ECF 20090508   @42128 Added inner class TempValidationHelper. Extends
    **                           RecordBuffer's implementation to override certain
    **                           methods.
    ** 071 ECF 20090610   @42634 Optimized copyAllRows(). Call BufferManager's
    **                           evictDMOIfUnused() method instead of incrementing
    **                           then decrementing DMO use counts.
    ** 072 ECF 20090701   @43223 Various fixes and optimizations. Changed
    **                           copyAllRows() method to use scrollable results
    **                           instead of a results list. Added table empty
    **                           tracking. Removed immediate flush concept.
    **                           Refactored initialization to be lazy, upon open
    **                           scope event.
    ** 073 ECF 20090717   @43230 Fixed regressions in #072 (@43223). Opening a
    **                           multiplex scope was unconditionally setting the
    **                           emptyTempTables flag for that multiplex ID, but
    **                           it must only do so if the reference count is 1.
    **                           When closing the last scope using a physical temp
    **                           table, we must remove all the rows before the
    **                           table is dropped, otherwise we can get into
    **                           trouble flushing deferred rollbacks.
    ** 074 ECF 20090724   @43423 Added getMasterBuffer() method. This is needed to
    **                           support proper rollback handling of shared temp
    **                           tables.
    ** 075 ECF 20090729   @43450 Fixed copyAllRows(). Call to ChangeBroker to
    **                           force a pending flush must come before the micro
    **                           transaction commit, otherwise the copied records
    **                           are lost. Also implemented no-op validate() call
    **                           for Context, which implements Commitable.
    ** 076 ECF 20090810   @43591 Changed to accommodate SessionListener API
    **                           change. sessionClosing() method was replaced with
    **                           sessionEvent().
    ** 077 CA  20090916   @43916 Fixed bulk delete for no-undo temp tables. On
    **                           commit, do not flush the bulk deletes for no-undo
    **                           temp tables, as this are relevant only for
    **                           undoable tables.
    ** 078 CA  20091001   @44061 Fixed leak caused by bulk delete duplication: do
    **                           not duplicate it if the record was deleted
    **                           explicitly (via a DELETE operation) or implicitly
    **                           (via a bulk delete).
    ** 079 CA  20091202   @44469 Reworked the rollback mechanism, to maintain
    **                           proper reversible order and thus to solve some
    **                           unique-constraint violations during rollback of
    **                           some bulk delete cases and mixed record operations.
    **                           The ReversibleChain is no longer used, instead a
    **                           linked set with all operations which affect a
    **                           certain table is used.
    ** 080 ECF 20130119          Changed the public API to this class; a number of
    **                           static methods have been removed and replaced
    **                           by the Buffer interface API. This enables more
    **                           natural, direct method calls on DMO proxy objects
    **                           for standard services. This entailed a change to
    **                           the DMO proxy implementation from the dynamic
    **                           proxy support in java.lang.reflect to that in
    **                           com.goldencode.proxy (we needed to inherit our
    **                           proxies from a functional base class).
    ** 081 SVL 20130223          Added createDynamicTable and associate(TableParameter ...).
    ** 082 VMN 20130407          Method copyAllRows() changes related with runtime support for
    **                           BUFFER-COPY (KW_BUF_COPY)
    ** 083 CA  20130610          Added APIs to populate or access temp table data using a result set.
    ** 084 SVL 20130822          Added openFreeScope, dropTable and DYNAMIC attribute.
    ** 085 CA  20131015          Added registration/deregistration of legacy names for a temp-table.
    ** 086 ECF 20131029          Import change and some methods made public for lock metadata support.
    ** 087 SVL 20131028          Added openScopeAt(), added legacyName and blockDepth parameter to
    **                           constructors and define(), added tableHandle(), refactored multiplex
    **                           scope closing.
    ** 088 CA  20131209          Added no-op deleted() method, required by the changes in Finalizable
    **                           interface.
    ** 089 SVL 20140106          Changes related to copyAllRows.
    ** 090 SVL 20140123          Added support for extents in copyAllRows.
    ** 091 SVL 20140124          getResource is used for unwrapping handles.
    ** 092 SVL 20140205          copyAllRows supports looseCopy and skipUniqueConflicts modes.
    ** 093 SVL 20140210          Use HQLExpression instead of HQL string.
    ** 094 CA  20140214          Fixed readAllRows - use the actual DMO, not the DMO proxy.
    ** 095 SVL 20140221          OutputTableHandleCopier was made external; changes in
    **                           createDynamicTable.
    ** 096 ECF 20140305          Removed explicit transaction processing from dropAllTables. Replaced
    **                           Apache commons logging with J2SE logging. Adapted for API changes in
    **                           RecordBuffer for copy operations.
    ** 097 OM  20140417          Dynamic temp-table buffers are directly available at the moment they
    **                           are declared instead of waiting for next block to open.
    ** 098 SVL 20140414          Use outputParameter instead of outputDMO + outputAppend in order to
    **                           support remote parameters.
    ** 099 CA  20140513          Added a weight for the context-local var, to ensure predetermined
    **                           order during context reset.
    ** 100 ECF 20140613          Fixed tableHandle to force the assignment regardless of pending error
    **                           status; simplified tempTableResource.
    ** 101 SVL 20140709          insertAllRows was properly implemented. Other changes related to
    **                           table parameters.
    ** 102 SVL 20140725          Fixed depth at which a buffer was opened in
    **                           createDynamicBufferForTempTable. Added an overloaded version of
    **                           TemporaryBuffer.define function.
    ** 103 ECF 20140814          Replaced RecordBuffer.flushAll with flush in copyAllRows method.
    **                           Added extra-transaction validation for no-undo temp-tables.
    ** 104 ECF 20140920          Changed to accommodate memory fixes in DynamicTablesHelper. Adjusted
    **                           openScope, openScopeAt, createTable to properly map table information
    **                           for DMOs shared across dynamic temp-tables.
    ** 105 OM  20141003          COPY and COMPARE buffer operations use legacy names for pairing
    **                           fields to be copied.
    ** 106 ECF 20141118          Fixed NPE in openMultiplexScope.
    ** 107 OM  20150107          Avoid double call of super.stateChanged(event) for INSERT events in
    **                           stateChanged() method. Added stack trace when detected database synch
    **                           is lost and documented event.
    ** 108 ECF 20150302          Registration of NoUndoValidator objects was too aggressive; only
    **                           register them when buffer scopes explicitly are opened. Prevent commit
    **                           or rollback when StopConditionException is thrown during copyAllRows.
    ** 109 OM  20150303          Added Dynamic Temp Buffers to HandleChain.
    ** 110 ECF 20150324          API change in TableMapper.
    ** 111 ECF 20150326          Added optional logging when dropping tables.
    ** 112 OM  20150504          Make sure the buffer is released when calling removeRecords().
    **                           Removed warning about incorrect caching.
    ** 113 ECF 20150516          Changed readAllRows to sort by primary index (defaulting to primary
    **                           key if no primary index is defined). Fixed dynamic resource cleanup.
    ** 114 ECF 20150520          Fixed a memory leak with static temp tables.
    ** 115 ECF 20150810          Minimize use of context-local lookups to improve performance.
    ** 116 ECF 20150910          Added getDynamicName to return name assigned with TEMP-TABLE-PREPARE.
    ** 117 ECF 20150929          Adjusted calls to modified BufferManager.evictDMOIfUnused method.
    ** 118 OM  20151026          Added support for writable UNDO attribute through UndoStateProvider.
    ** 119 GES 20151203          Javadoc fix.
    ** 120 CA  20151219          Fixed conversion of shared buffers.  Fixed runtime of global
    **                           temp-tables.  Added getAllSlaveBuffers().
    ** 121 ECF 20160225          Changes required by new PropertyHelper implementation.
    ** 122 ECF 20160320          Fixed regression in copyAllRows and streamlined the copy to use a
    **                           simple Undoable.assign in the event the source and target buffers
    **                           have the same backing DMO implementation class. Fixed memory leak
    **                           in readAllRows and copyAllRows.
    ** 123 OM  20160316          Changed signature of listDirtyIndexes() to match super method.
    **     ECF 20160430          Performance improvement: avoid database access when we know table is
    **                           empty in record copy and removal methods. Bypass Hibernate and use
    **                           native SQL where possible, to avoid unnecessary overhead.
    ** 124 ECF 20160512          Optimized removeRecords to use SQL on update when no HQL where
    **                           is provided. Modified getPrimaryOrderByClause to deal with null
    **                           primary index. Migrated more HQL to SQL.
    ** 125 ECF 20160603          Minor performance fixes.
    ** 126 ECF 20160613          Fix removeRecords regression from #125. Fixed ReversibleBulkDelete
    **                           for no-undo temp-tables.
    ** 127 OM  20160629          HQLPreprocessor needs the list of fields of the current index.
    ** 128 ECF 20160720          Fixed buffer lookup during HQL preprocessing.
    ** 129 CA  20160728          Expose the temp-table global state, to be available for undoable
    **                           support.
    ** 130 ECF 20160822          Avoid deregistering multiplexer in global scope.
    ** 131 ECF 20160829          Performance enhancements. Use local BufferManager to check
    **                           transaction status instead of TransactionManager. Session flushing
    **                           improvements to allow better batching of database updates.
    ** 132 ECF 20161004          Use implicit transactions where possible to reduce flushing.
    ** 133 ECF 20171028          Refactored readAllRows to make row handling flexible.
    **     CA  20171030          Added support for BY-REFERENCE table parameter mode.
    **                           Added support for REFERENCE-ONLY temp-table option.
    ** 134 GES 20171207          Removed forced version of assign().
    **     ECF 20171213          Added query substitution parameter error handling to bulk delete.
    ** 135 OM  20171212          HQLPreprocessor.get() signature change. Added delete() variant with
    **                           support for external buffers for nested selects.
    **     ECF 20171217          Enable DMO alias qualifier to be forced in certain case in
    **                           removedRecords method.
    ** 136 ECF 20180512          Fixed empty table check in removeRecords.
    ** 137 ECF 20180609          Reduce excessive transaction commits.
    ** 138 CA  20180525          Fixed removeRecords - alias was being set twice.
    ** 139 ECF 20181221          Adjust for inlined substitution parameters in removeRecords.
    ** 140 OM  20190129          Added ref-counting for oo fields.
    **     CA  20190311          A buffer may be active as the source for multiple output table
    **                           parameters.
    **     ECF 20190313          Minor optimizations.
    **     ECF 20190323          Avoid managing undoable resources if no changes have been made by a
    **                           buffer instance.
    **     CA  20190325          Implemented ProcedureHelper instead of direct usage of the static
    **                           API.  This allows the elimination of context local usage in
    **                           ProcedureManager.
    **     ECF 20190325          Replaced TransactionManager static calls with TransactionHelper
    **                           instance calls to reduce context-local lookups.
    ** 141 SVL 20190614          Support for CLOB code pages.
    ** 142 CA  20190628          blob and clob fields must be read, before sending them to the remote
    **                           side (for appserver calls).
    ** 143 OM  20190510          Added error management in getDefaultBuffer().
    **     OM  20190523          Unified parameter options handling for TempTables and DataSet params.
    **     OM  20190611          Added support for BEFORE/AFTER buffers.
    ** 144 ECF 20190714          Added support for aggressive buffer flushing mode.
    **     SBI 20190807          Changed removeRecords to fix generated sql code.
    **     ECF 20190813          Change to RecordBuffer.release API.
    ** 145 CA  20190722          Fixed a case of BY-REFERENCE table argument.
    **     CA  20190723          OUTPUT TABLE-HANDLE argument must get a copy of a TABLE parameter.
    **     CA  20190726          tableHandle() must look at the master buf, if is set. other created
    **                           buffers will not have the tempTableRef set.
    **     CA  20190728          For BY-REFERENCE/REFERENCE-ONLY transfer the buffer's record, too.
    **     EVL 20190729          Fix from CA to skip same reference processing in associate() method.
    **     CA  20190812          Wrap the DMO proxy in a special proxy which allows runtime change
    **                           of the DMO; this allows to make the instance mutable, and provides
    **                           support for BY-REFERENCE and REFERENCE-ONLY options.
    **     CA  20190816          Static temp-tables are now created when their default buffer is
    **                           defined. Fixed some issues with the mutable buffers - not all need
    **                           to be mutable (especially the dynamic ones created for a dynamic
    **                           temp-table).
    ** 146 SVL 20190801          Fixes for COLUMN-CODEPAGE, especially for shared tables.
    ** 147 OM  20190909          Added support for ERROR-FLAG and ERROR-STRING attributes.
    **     CA  20191009          Added where and sort clause translation in case of bound buffers.
    ** 148 CA  20191119          Track the executing legacy class, to be able to register any buffer
    **                           to its source definition class.
    **                           Added support for before-table meta information, when creating a
    **                           temp-table from a remote (appserver) parameter.
    **     CA  20191230          Fixed reversible bulk delete - needs the preprocessed WHERE clause,
    **                           and proper tracking of the hasWhere flag.
    ** 149 AIL 20200108          Prevented direct access to record buffer members.
    ** 150 AIL 20200113          Changed delete signature to match the override.
    **     CA  20200110          More fixes for TABLE parameters and resource delete.
    ** 151 CA  20200323          A fix for binding a source buffer's DMO alias to the destination: this
    **                           must be permanent only in case of BIND arguments.  Otherwise, the
    **                           DMO alias binding is limited only for the duration of that top-level
    **                           block call.
    ** 152 IAS 20200413          Fixed DMO instantiation on SESSION:DATE-FORMAT.
    **     CA  20200427          Allow a TABLE to be read from JSON argument, in case of a remote REST call.
    **     CA  20200514          Added support for SOAP web services (field are sent as BDT already, but the
    **                           'fromJson' flags remains).
    ** 153 ECF 20200419          Removed Hibernate dependencies.
    **     CA  20200622          Small performance improvement - use IdentityHashMap if the key is java.lang.Class.
    **                           Fixed removeRecords - FQL requires 'AS' in the FROM clause for DELETE.
    ** 154 SVL 20200913          isTemporary became public.
    **     ECF 20200917          Backed out bracketing of DMO instantiation with SESSION:DATE-FORMAT use,
    **                           as it no longer had an effect with recent ORM changes. Implemented forced
    **                           NO-UNDO mode.
    **     OM  20200919          Improved access to dmo metadata.
    **     CA  20200922          Optimized performance of record copy (be it from parameter access or buffer-copy),
    **                           by avoiding the RecordBuffer$Handler overhead, for non-indexed, non-mandatory,
    **                           non-trigger properties - for these, the access (getter or setter) is done
    **                           directly via the record's datum.
    **     OM  20200924          Index components carry multiple information to avoid map lookups for them.
    **     CA  20200924          Fixed a problem with CA/20200922 - when the DMO is a proxy, access to fields must
    **                           be done via getter methods, to allow the proxy to de-reference the proper bound
    **                           buffer instance.
    **                           Replaced Method.invoke with ReflectASM.
    **                           Replaced DMO property access from reflection to property's data handler.
    **     OM  20201001          Improved DMO manipulation performance by caching slow Property annotation access.
    **     CA  20201003          Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
    **                           Use an identity HashSet where possible.
    **     CA  20201005          Fixed bulkCopyAllRows and copyAllRows - H2 will now match the WHERE and ORDER BY
    **                           indexes, avoiding re-sorting the fetched rows.
    **     AIL 20201031          Added sorted and direct keywords for insert into select from.
    **     AIL 20201118          Made extent fields bulk copy use insert into select from.
    **         20201125          Added fast copy validation using DMO signature.
    **         20201201          Moved fast copy implementation to FastCopyHelper. Added helper methods.
    **     CA  20201202          Record change notifications are processed only if the multiplex ID matches with
    **                           the event's source.
    **     AIL 20201215          Added before table prop mapping to the fast-copy helper call.
    **         20201216          Added support for fast loose-copy.
    **         20201228          Invalidated fast-find cache after fast-copy.
    **     CA  20201118          Fixed a leak related to cleaning up the multiplex scope when deleting a static
    **                           temp-table part of a persistent external program.
    **     OM  20201120          Fixed buffer copy methods. Fixed rejectChanges() method. Fixed management of
    **                           BEFORE-BUFFER attributes. Fixed buffer/table copy.
    **     OM  20201218          Fixed implementation for error and rejected attributes/hidden fields.
    **     OM  20210223          Invalidate index-based cache in the event of low-level SQL operations.
    **     CA  20210304          Fixed date, datetime and datetimetz literal parsing.
    **     OM  20210328          Registered buffer with parent Temp-Table.
    **     OM  20210404          The getCodePage() method resolve CP to CPINTERNAL instead of returning null.
    **     CA  20210412          Fixed BUFFER:COPY-TEMP-TABLE in REPLACE mode (if the target has no record matching
    **                           a source record, then this must not be shown as an error message - instead, use
    **                           'nestedSilent' for this query).
    **     OM  20210503          The components of ORDER BY clause used by readAllRows() must be property names
    **                           not SQL column names. They will be converted to SQL later, in FqlToSqlConverter.
    **     RFB 20210504          Temporarily undo OM changes until the regression can be determined.
    **     ECF 20210504          BufferManager API name change.
    **     ECF 20210506          Use RecordBuffer.getDmoInfo() getter instead of direct field access to
    **                           prevent NPE in proxy case.
    **     CA  20210508          Child shared buffers or temp-tables act like REFERENCE-ONLY, where they are bound
    **                           to the master buffer/temp-table.
    **     ECF 20210511          Replaced DynamicTablesHelper.normalizeName with TextOps.rightTrimLower.
    **     CA  20210516          During BROWSE population, if the query uses other buffers than the original ones,
    **                           the original buffers (only temporary and for static BROWSE only) will be bound to
    **                           the query's buffers.
    **     SVL 20210517          In createDynamicTable check if the source table is valid.
    **     SVL 20210517          Update to the previous change in createDynamicTable.
    **     OM  20210521          DELETE and CREATE return the proper logical value.
    **     ECF 20210606          Use the master buffer's global setting in both useShared method variants, when
    **                           creating a child shared buffer. This was previously set to true always, causing
    **                           all these buffers to be stored by BufferManager in the global scope. This created
    **                           a large memory leak over time, as they would never be cleaned up.
    **     ECF 20210612          Deregister a buffer from the tempTableRef AbstractTempTable when it goes out of
    **                           scope to prevent a memory leak.
    **     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
    **                           initialized at the method's execution, and not at the caller's arguments.
    **     SVL 20210614          Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT
    **                           parameters with no input data rows, which allows to access table structure if
    **                           non-unknown table/dataset handle has been passed from the calling side.
    **     CA  20210625          If the remote table has no name, do not create it in case of OUTPUT parameters.
    **     CA  20210626          Do not delete the master shared temp-table until the last of its associated
    **                           buffers (i.e. child shared temp-tables) is deleted.
    **     ECF 20210820          Retrofit for query substitution parameter preprocessing fixes.
    **     CA  20210831          Fixed insertAllRows, the before-table must track if there records in the source
    **                           table, so will not be marked as 'definitely empty'.
    **     OM  20210831          Avoid NPE caused by transient peer records when before record attributes are set.
    **     OM  20210904          Force saving records whose before-table attributes are set and records were NOT
    **                           initially marked as CHANGED.
    **     OM  20211005          Registered the de-association of table parameters with ProcedureManager instead
    **                           of a finalizable in TransactionManager.
    **     CA  20210929          Fixed cases when rowid function is used in the pairs for BUFFER-COPY or
    **                           SAVE-ROW-CHANGES.
    **     OM  20211102          Optimized DELETE does not work correctly for changes-tracked dataset tables.
    **     CA  20211112          Allow the DATASET and TABLE parameters for remote SOAP calls to be transferred
    **                           via XML (so that relations, schema, etc is done on the server-side).
    **     CA  20211116          Fixed a NPE related to CA/2021112.
    **     OM  20211209          The dynamic buffers for TempTables are opened at global level (0), same as for
    **                           permanent tables.
    **     OM  20211020          Added _DATASOURCE_ROWID property.
    **     OM  20211217          Remove temp-table from TableMapper if its multiplex is not in use anymore.
    **     CA  20211222          Fixed memory leak: remove the temp-table from the domains map, on delete;
    **                           invalidate the FastFindCache for the temp-table, when its multiplex scope is
    **                           closed.
    **     CA  20211227          writeToXml() must return null for invalid handle.
    **     CA  20220104          Fixed createRecords() to be aware of the added _DATASOURCE_ROWID property.
    **     CA  20220105          readFromXml must force UTF-8 as codepage at the longchar created to hold the
    **                           XML string.
    **     CA  20220110          Refactored the temp-table's row-meta state to use constants for the indexes in
    **                           the array used for serialization.
    **     OM  20220115          Added reminder comment for possible flaw in useShared() method.
    **     CA  20220214          The slave buffers are cleaned up only when the associated temp-table gets deleted.
    **     CA  20220210          Removed Commitable.rollbackPending and its notifications, as there is no actual
    **                           implementation of this method.
    **     CA  20220309          The PKs are reclaimed using a (DMO, multiplexID) pair as key.
    **     CA  20220318          Fixed insertAllRows, when the before-table has ROW-DELETED records (this have no
    **                           correspondent in the peer table).
    **     CA  20220309          If the temp-table is currently being queried, force a loop delete when emptying
    **                           the table, and also force a query loop when creating the records (bypass
    **                           FastCopyHelper).
    **     OM  20220327          Use the implicit primary index if none expressly defined for table iterations.
    **     CA  20220329          Switched the condition in delete(DataModelObject[], String, Object...), a small
    **                           performance improvement.
    **     CA  20220330          Fixed a regression in CA/20220309 - nextPrimaryKey() will consume pending
    **                           reclaimed PKs until they are exhausted.
    **     CA  20220627          A CREATE BUFFER statement will use the bound buffer instead of the default buffer
    **                           for the temp-table.
    **     OM  20220707          Shared meta-knowledge about fields and properties of a record was collected in
    **                           DmoMeta to avoid duplicating their collection and storage.
    **     CA  20220830          Temp-tables sent via a web-service (like REST) may not contain all the fields,
    **                           and the missing fields will default to unknown.
    **     VVT 20221003          CommonHandle.getResourceType() method renamed to resourceType() to prevent
    **                           conflicts with namesakes in DMO. See #6694.
    **     CA  20220906          BufferManager's undoable support is no longer being used, so it was removed.
    **                           Extracted all BufferManager state which requires notification for
    **                           'isImportantBlock' to an external TxWrapper class (including the private class
    **                           here), to allow the scopeable BufferManager state related only to buffers (and
    **                           not transaction related) to be registered for scope notifications in a lazy
    **                           manner, when buffers are accessed/used/opened.
    **     CA  20221006          Added JMX instrumentation for buffer definitions (dynamic and static). Refs #6814
    **                           Use a Supplier for the proxy plugin instance, as when the proxy is already
    **                           created, there is no need to use this instance.  Refs #6822
    **                           Refactored TableMapper for temp-tables to keep a cache of LegacyTableInfo and
    **                           the mutable state in MutableFieldInfo, for the actual TempTable instance.
    **                           Refs #6825
    **                           Do not access tableHandle() in the FWD runtime, get the TempTable instance
    **                           directly.  Refs #6826
    **     CA  20221007          Added a counter for the number of bindings of this buffer, when passed as a
    **                           parameter and bound to another buffer.
    **     OM  20221012          The TriggerTracker are bound to DMO, not to the buffer they are contained into.
    **     OM  20221019          Do not assign new PK for replaced records in copyAllRows().
    **     CA  20221031          Added JMX instrumentation for TABLE[-HANDLE] parameter processing.
    **     TJD 20220504          Java 11 compatibility minor changes.
    **     OM  20221104          Logged mapping error in createRecords(). Avoiding possible NPE.
    **     OM  20221125          Handled BINDing of REFERENCE-ONLY OUTPUT buffers.
    **     AL2 20221205          Replaced AdaptiveQuery with PreselectQuery for loopDelete.
    **                           Use null sort instead of empty sort for PreselectQuery/
    **     OM  20221220          Simlpified TempTableBuilder.getOrderedPropertyNames() signature/implementation.
    **     CA  20220524          An explicit buffer created for a temp-table must save the tempTableRef from the
    **                           master.
    **     GES 20210430          Reworked associate() and createDynamicTable() to remove unnecessary arguments.
    **     CA  20220603          The before-table record on a DELETE event must be created regardless if the
    **                           buffer is attached to a dataset or not.  Commented the _originRowid and
    **                           _datasourceRowid setters for the before-record associated with a DELETE (needs
    **                           confirmation).
    **     CA  20220609          A fix for 'OUTPUT TABLE tt1 BIND' argument, where the table arg is REFERENCE-ONLY.
    **     CA  20220613          registerBindingBuffer must use as procedure the SOURCE-PROCEDURE in case of
    **                           OUTPUT and THIS-PROCEDURE in case of INPUT, for BIND parameters.
    **     CA  20220727          Fixed OO dataset/table parameters (at the method definition and method call) when
    **                           there is an APPEND option.
    **     CA  20220909          The 'doNotProxy' method array must be a constant, otherwise it will leak at the
    **                           ProxyFactory$CacheKey.DO_NOT_PROXY_HASH_CODES.
    **                           If a buffer is created with a dynamic master and the buffer is not dynamic, then
    **                           the 'global' flag must be flase.  This fixed a Multiplexer leak in
    **                           TransactionManager$WorkArea.globalBlock.finalizables.
    **     CA  20220918          Replaced 'emptyTableFlags' with a bitmap 'nonEmptyTableFlags', where only
    **                           temp-tables which have records are tracked (by their mutiplex IDs).
    **                           Delete the dynamic query at copyParentUnchangedRecords.
    **                           Only global or dynamic tables are registering a global dynamic multiplexer.
    **                           When a temp-table is deleted or emptied, clear the ORM session of its cached
    **                           records and the reclaimed keys.
    **     CA  20220919          Auto-delete a bound buffer when the definition gets deleted, if the bound buffer
    **                           was created internally by FWD.
    **                           When a BIND parameter association is being performed, check if the definition is
    **                           REFERENCE-ONLY.
    **     CA  20220920          DMO interface must be used on ReclaimTable constructor, and not buffer DMO iface.
    **     VVT 20220913          CommonHandle.getResourceType() method renamed to resourceType() to prevent
    **                           conflicts with namesakes in DMO. See #6694.
    **     CA  20221007          Added a counter for the number of bindings of this buffer, when passed as a
    **                           parameter and bound to another buffer.
    **     CA  20221014          In 'doCloseMultiplexScope', 'pruneSessionCache' must be called only if the table
    **                           truly empty; otherwise, buffers may remain with records marked as 'cached' when
    **                           they are not.
    **     OM  20221027          Use Dialect API to create and drop sequences.
    **     IAS 20221102          Added error reporting for SCHEMA-MARSHAL == NONE.
    **     OM  20221103          New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache.
    **     IAS 20221103          Fixed support for a binded table parameter.
    **     CA  20221206          If a REFERENCE-ONLY table is already bound to the same target table, do not
    **                           re-bind.
    **     RAA 20230109          Changed inline statement(s) to prepared statement(s).
    **     CA  20230110          'getAllSlaveBuffers' returns null if no buffers are found.
    **     SVL 20230110          P2JIndex.components() provides direct access to the components array in order to
    **                           improve performance.
    **     AL2 20221205          Replaced AdaptiveQuery with PreselectQuery for loopDelete.
    **     SVL 20230113          Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     HC  20230116          Replaced some handle usages with the actual wrapped resources for
    **                           performance.
    **     CA  20230116          Avoid using handle.unwrap, handle.getReference or other BDT usage from within
    **                           FWD runtime.
    **     HC  20230118          Eliminated some of the uses of String.toUpperCase and/or
    **                           String.toLowerCase for performance.
    **     CA  20230208          Backed out changes in 'OM  20221125          Handled BINDing of REFERENCE-ONLY
    **                           OUTPUT buffers.' as this causes regressions.
    ** 155 RFB 20230303          Condition under which NO-SCHEMA-MARSHAL is thrown needed to be more stringent.
    **                           If a result set was received, then it must have the schema. Ref. #7145.
    ** 156 CA  20230215          Improved other cases to reduce BDT usage from within FWD runtime.
    ** 157 RAA 20230314          Re-factored some fields in removeRecords so they are now part of prepared
    **                           statements rather than being hard-coded.
    ** 158 RAA 20230316          Added an initial capacity to the list of additional arguments in removeRecords.
    ** 159 DDF 20230316          Added definitelyHasRecords().
    **     DDF 20230303          Only allow computed column prefix if dialect allows so (refs: #7108).
    ** 160 OM  20230215          Handled collision of temporary buffer names by temporarily overriding them.
    ** 161 GBB 20230410          Using ErrorManager.displayError() overloaded version with less params.
    ** 162 SR  20230510          Matched persistent.list() new signature.
    ** 163 IAS 20230422          'readAllRows' returns number of processed records.
    ** 164 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 165 DDF 20230523          Added looseCopy parameter to executeCopy call.
    ** 166 CA  20230523          In ReferenceProxy, cache the DMO property names and the relation between the
    **                           definition DMO and the bound DMO property names.
    ** 166 CA  20230530          Unbind an explicit buffer if the temp-table is not REFERENCE-ONLY.
    **                           definition DMO and the bound DMO property names.
    ** 167 OM  20230529          Using loop delete when removing records in a after buffer with tracking on.
    ** 168 AL2 20230608          Use timerWithReturn on define() to avoid creating extra results array.
    **     AL2 20230613          Use the existent buffer manager and procedure helper where possible.
    ** 170 DDF 20230620          Made FORCE_NO_UNDO_TEMP_TABLES non-final, renamed it.
    **     DDF 20230627          Made forceNoUndoTempTables accessible through a method.
    ** 171 SR  20230614          Updated copyAllRows to allow fast-copy in replace mode if destination is empty.
    **     SR  20230621          In copyAllRows, if simpleCopy is true looseCopy should be false.
    **         20230704          Let simpleCopy be true even when the sqlSignature match.
    **     RAA 20230807          Compressed FastCopyHelper usage into one function call.
    **     RAA 20230823          Made fastCopyCache contextual, meaning that it is now stored in Context.
    **     RAA 20230823          When replacing a record in the destination table, its primary key is no longer
    **                           changed with the one from the source record.
    **     RAA 20230828          Handled potential flag changes before using the fastCopyCache.
    **     RAA 20230828          Extracted local context from the source buffer instead of function call.
    **     RAA 20230830          Matched the behavior of master copy for before copy when using FastCopyHelper.
    **     RAA 20230904          Removed FastCopyHelper.finishCopy usage.
    **     AL2 20230905          Fixed noMapping flag to be part of the helper, not the context.
    **                           Removed replaceMode from fast-copy as it is not supported.
    ** 172 CA  20230907          Reworked the binding rules: if the buffer is not already bound (implicitly or
    **                           explicitly), then the actual target will be bound to the source, while the rest
    **                           of the buffers for this table (explicitly defined or the default buffer) will
    **                           have a new buffer created.
    ** 173 OM  20230915          Added support for generic session triggers.
    ** 174 GBB 20231011          SecurityManager session methods calls updated.
    ** 175 OM  20231011          Fixed scope of attached buffers.
    ** 176 AL2 20231013          Removed in-memory key reclaiming. Allowed FWD-H2 to do the reclaim in-house.
    ** 177 AL2 20230926          Reworked definatelyHasRecords into fastHasRecords. This returns only if we can
    **                           make a sure statement on the fact that the underlying table has records or not.
    ** 178 AL2 20231101          Set-up the fast-copy mapping tables only once, not at each copy attempt.
    ** 179 AL2 20231103          Made the dropMappings use the session instead of the persistence.
    ** 180 DDF 20230818          Generate a temporary sequence for temporary tables.
    ** 181 AL2 20231116          Notify the direct-access driver that a multiplex scope was closed.
    ** 182 AL2 20231127          loopDelete should not invalidate records while using a PreselectQuery.
    ** 183 CA  20231129          Moved the LegacyTableInfo instance from TableMapper to AbstractTempTable (only
    **                           for temp-tables).
    ** 184 ES  20231203          Removed query.next() from loopDelete method forEach inner block.
    ** 185 OM  20240117          The unbound associated buffers are open at parent scope (-1).
    ** 186 AD  20240125          Optimized delete queries to remove where condition when possible.
    ** 187 HC  20240222          Enabled JMX on FWD Client.
    ** 188 CA  20240302          A BIND at the param or argummet has effect only if either the argument has
    **                           BY-REFERENCE or the target is REFERENCE-ONLY. Removed the 'bindBuffer' support
    **                           which sets and unsets the DMO alias as this was determined to be obsolete and/or
    **                           incorrect.
    **     CA  20240303          Mark as REFERENCE-ONLY the explicit buffers, too, for a temp-table.
    **                           DATASET's buffers are set as the definition buffer, and not the outer
    **                           ReferenceProxy instance - resolve the actual bound buffer when associating a
    **                           temp-table.
    ** 189 CA  20240321          Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for',
    **                           where it applies.
    ** 190 AL2 20240315          Improved transaction handling when flushing the buffers before copyAllRows.
    ** 191 ES  20240405          In createDynamicTableImpl table handle should be assigned to handle in case
    **                           of BIND option too.
    ** 192 ES  20240408          In case of a bind temp-table in a procedure, the table should remain bind after
    **                           the execution of the procedure.
    ** 193 CA  20240410          Flush the buffers in 'copyChanges' (called by BUFFER:GET-CHANGES).
    ** 194 CA  20240422          Savepoints are used to rollback undoable temp-tables, and no-undo support is
    **                           builtin into H2 itself; so, removeRecords will always perform an actual DELETE.
    ** 195 CA  20240502          Fixed 'removeRecords' where the FQL was prepared with a faulty alias.
    ** 196 AL2 20240512          Removed bulk option from nextPrimaryKey.
    ** 197 AL2 20240514          Removed any usage of sequences as records have their id provided by the database.
    ** 198 CA  20240514          Avoid flushing both the before and after buffers in 'copyChanges', as
    **                           'flushBuffers' now will take care of flushing both tables.
    ** 199 SP  20240527          Avoid updating after buffer ERROR-FLAG and ERROR-STRING attributes when
    **                           before buffer row state is ROW_DELETED.
    **     SP  20240527          When deleting a record from the after buffer, also delete it from the before buffer
    **                           if before.rowState = ROW_CREATED, regardless of TRACKING-CHANGES value.
    ** 200 AL2 20240618          Changed initialize signature to include fromDefine parameter.
    ** 201 SP  20240611          When deleting a record from the after buffer, also delete it from the before buffer
    **                           if TRACKING-CHANGES is false.
    ** 202 ES  20240626          Avoid logging DirectedAccessExceptions in expected cases.
    ** 203 CA  20240627          Explicitly delete all records loaded in the buffers before emptying the temp-table,
    **                           as a NEW record loaded in a buffer will have just the PK row in the temp-table.
    **     CA  20240701          In 'clearBuffers', mark the before-buffer in 'setup', so the delete can happen
    **                           regardless of TRACKING-CHANGES state.
    **     AL2 20240808          Overridden cleanup to remove buffer from slave collection when out-of-scope.
    ** 204 RAA 20240808          Adjusted FastFindCache to be retrieved from the persistence context.
    **     DDF 20240808          The database should be retrieved using the existent persistence context.
    ** 205 SP  20240806          Moved code from define() to defineImpl() method.
    **                           Skip using JMX timers when JMX_DEBUG flag is not set.
    ** 206 LS  20240904          Changed useShared() to set autoDeleteBinding flag to false for DEF SHARED BUFFER.
    ** 207 SP  20240910          Unreserve PK of newly created record in copyAllRows when validation fails.
    ** 208 OM  20240909          Improved Database API. Concurrent multitenancy functionality implementation.
    ** 209 CA  20240924          Do not destroy the L2/L3 FastFindCache(s), until the DMO gets out of scope.
    **     CA  20230924          Further reduce context-local lookup.
    ** 210 AL2 20240726          If the associate uses the same tables, skip the associating process.
    **     TJD 20240729          Removed AL2 20240726 changes due to regression
    **     TJD 20240902          forceLoopingDelete when there is any DMO in RecordNursery related to this Buffer
    ** 211 ES  20240919          Remove multiplexID in loopDelete.
    ** 212 ICP 20241126          Fixed conditions for throwing 11885 legacy error in copyAllRows.
    ** 213 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
    **                           based on [sharedDb] parameter.
    ** 214 ICP 20241202          In copyAllRows, refined conditions for throwing 11885 legacy error and added
    **                           error throw when the copy fails. Fixed createPropsMap to also match INT and
    **                           INT64 types to match legacy behaviour.
    ** 215 SP  20241106          Changed 'removeRecords' to always use an alias in the prepared FQL.
    ** 216 AS  20250131          Added the deleted flag, indicating if a buffer was deleted as a resource.
    **                           Improved slave buffers cleanup.
    ** 217 ES  20250130          Override the dmoAlias of the source buffer in copyAllRows and restore it at the
    **                           end of the buffer.
    ** 218 DDF 20250317          Make sure all fields that are not part of an index are flushed when copying rows
    **                           from the source to the destination buffer.
    **     DDF 20250318          Check the RecordNursery after the buffer is flushed.
    ** 219 CA  20250429          'removeRecords', if there is a WHERE clause, must evict all cached records which
    **                           are being deleted.
    **     LS  20250502          Removed 'doesReclaimKeys'.
    ** 220 ES  20250509          Added mutableBufferProxy method to create a proxy which has as super class the proxy
    **                           class from previous level and no plugin.
    ** 221 RNC 20250520          Append the property name instead of the column name when generating the ORDER BY
    **                           clause based on the primary index.
    ** 222 SP  20250606          Modified readAllRows and getPrimaryOrderByClause to append a table alias when
    **                           generating the ORDER BY clause.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.persist;

    import org.roaringbitmap.*;
    import java.lang.InstantiationException;
    import java.lang.reflect.*;
    import java.sql.*;
    import java.util.*;
    import java.util.concurrent.atomic.*;
    import java.util.function.*;
    import java.util.logging.*;

    import com.goldencode.cache.Cache;
    import com.goldencode.p2j.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.directory.Base64;
    import com.goldencode.p2j.jmx.*;
    import com.goldencode.p2j.persist.FastCopyHelper.FastCopyContext;
    import com.goldencode.p2j.persist.FastCopyHelper.FastCopyKey;
    import com.goldencode.p2j.persist.TableMapper.LegacyFieldInfo;
    import com.goldencode.p2j.persist.event.*;
    import com.goldencode.p2j.persist.lock.*;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.orm.types.*;
    import com.goldencode.p2j.persist.trigger.DatabaseEventType;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.ErrorManager;
    import com.goldencode.p2j.util.logging.CentralLogger;
    import com.goldencode.proxy.ProxyFactory;
    import com.goldencode.util.*;

    /**
     * A specialized record buffer implementation which uses a temporary table
     * as backing storage and supports Progress' temp/work-table semantics.  A
     * new instance is created by invoking the static {@link #define} method,
     * which, like the superclass' method of the same name, creates a proxy
     * object which client code uses to interact with the database.
     * <p>
     * <b>Implementation Notes</b><br>
     * <b>Multiplexing.</b>  Because of limitations imposed by Hibernate's lack
     * of direct support for temp tables, a single temp table instance in the
     * backing database is multiplexed if multiple buffers of the same type of
     * DMO class are opened simultaneously in the same context.  This can happen
     * either when calling code explicitly defines multiple temp table buffers
     * using the same DMO type, or when method calls pass a temp table DMO proxy
     * as a parameter.  Multiplexing is used to enable Progress' semantic of
     * copying data between temp tables when they are passed as parameters to
     * nested procedure calls.
     * <p>
     * Multiplexing is achieved by adding a multiplex ID column to every temp
     * table schema.  Each buffer instance is assigned a unique multiplex ID the
     * first time a scope is opened on it.  Every access to the backing temp
     * table (i.e., queries, updates, inserts, deletes) take the multiplex ID
     * into account, such that the backing table is managed as multiple, virtual
     * table instances, one per distinct multiplex ID.
     * <p>
     * A temp table is created (along with its indexes, if any) the first time a
     * particular DMO type is accessed.  It is only dropped when no buffers
     * reference it.  As buffers go in and out of scope, a context local "in-use"
     * map is updated to reflect which buffers reference which physical temp
     * tables.  When a buffer goes out of scope, cleanup processing checks
     * whether it is the last buffer to reference its backing table.  If so, the
     * table is dropped.  If not, the table is not dropped, but all records with
     * that buffer's multiplex ID are removed from the table.
     * <p>
     * Temporary buffers may be scoped globally, or to the top-level (i.e.,
     * method) scope.  If global, cleanup is performed only when the context
     * itself is about to go out of scope.  If not global, cleanup is performed
     * when the method scope is popped by the transaction manager.  The latter
     * coincides with the external procedure scope in Progress.
     * <p>
     * There is a notion of a <i>master</i> buffer and <i>slave</i> buffers.  The
     * former is created as the result of converting a DEFINE TEMP-TABLE Progress
     * statement.  The latter is created as the result of converting a DEFINE
     * BUFFER FOR <i>temp-table</i> Progress statement.  A master buffer and all
     * of the associated slave buffers share the same multiplex ID, since these
     * buffers must all point to the same backing, virtual temp table for their
     * backing data.  The first time any buffer in such a group has a multiplex
     * ID assigned, all buffers in the group have their multiplex IDs assigned to
     * the same value.
     */
    public final class TemporaryBuffer
    extends RecordBuffer
    {
       /** Name of the multiplex ID field in temp table DMO classes */
       public static final String MULTIPLEX_FIELD_NAME = "_multiplex";

       /** Error text for error 12324. */
       public static final String ERROR_MISMATCHED_FIELDS = "Remote mismatched fields or " +
          "mismatched BEFORE-TABLE attribute for temp-table parameter or dataset member parameter";

       /** Error text template for error 8030. */
       public static final String ERROR_TABLE_MISMATCH_TEMPLATE = "Parameter number %d (table %s) " +
          "mismatch. Has %d fields - client schema has %d fields";

       /** Error text template for error 8029. */
       public static final String ERROR_EXTENT_MISMATCH_TEMPLATE = "Table %s mismatch: The extent " +
          "number of field %d does not match the local definition";

       /** Error text template for error 8028. */
       public static final String ERROR_FIELD_TYPE_MISMATCH_TEMPLATE = "Table %s  mismatch: The " +
          "client data type of field %d does not match the definition on the AppServer";

       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(TemporaryBuffer.class.getName());

       /** Instrumentation for {@link #define}. */
       private static final NanoTimer DEF_BUFFER = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmBufferDefineTemp);

       /** Instrumentation for {@link #createDynamicBufferForTempTable}. */
       private static final NanoTimer CREATE_BUFFER = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmCreateDynamicBuffer);

       /** Instrumentation for {@link #associate}. */
       private static final NanoTimer STATIC_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmTempTableParam);

       /** Instrumentation for {@link #createDynamicTable}. */
       private static final NanoTimer DYN_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDynamicTableParam);

       /** Multiplex ID sets currently in use for this context by DMO interface */
       private static final ContextLocal<Context> context = new ContextLocal<Context>()
       {
          @Override
          public WeightFactor getWeight() { return WeightFactor.LEVEL_2; }
          protected Context initialValue() { return (new Context()); }
       };

       /** Names of tables which are not NO-UNDO when forcing NO-UNDO temp-table behavior globally */
       private static final Set<String> forceNoUndoConflicts = new HashSet<>();

       /** Does this buffer represent a global temp table? */
       private boolean global = false;

       /** Can property changes on this buffer be undone? */
       private UndoStateProvider undoStateProvider;

       /** Context associated with this buffer instance */
       private final Context local;

       /** Constructor used to create new instances of the backing DMO */
       private Constructor<? extends Record> dmoCtor = null;

       /** SQL used to check whether a record was deleted by a bulk delete */
       private String bulkDeleteEventSQL = null;

       /** Destination table parameters for output copy if necessary before scope closes */
       private Set<TableParameter> outputParameters = null;

       /** Multiplex ID assigned to this buffer instance */
       private Integer multiplexID = null;

       /** Globally-scoped multiplexer for dynamic temp-tables ({@code null} for others) */
       private Multiplexer dynamicMultiplexer = null;

       /** Master buffer for this buffer;  null indicates this is the master */
       private TemporaryBuffer master = null;

       /** Reference to parent temp table; if master buffer exists this field is <code>null</code> */
       private TempTable tempTableRef = null;

       /** Order by clause associated with primary index of backing temp-table, if any */
       private String primaryOrderBy = null;

       /** Flag indicating if REFERENCE-ONLY option was set for this TEMP-TABLE. */
       private boolean referenceOnly = false;

       /**
        * Map of code pages of the CLOB fields, which were declared with COLUMN-CODEPAGE option.
        * Code pages will be evaluated and applied in {@link #openScope()}. Keyed by hibernate
        * property names of the CLOB fields.
        */
       private Map<String, Supplier> registeredCodePages = null;

       /**
        * The handler used to bound to another instance at runtime.  This is saved only for the
        * instance created when the buffer was made mutable, at the external program/legacy OO class
        * instantiation.
        */
       private ReferenceProxy mutableHandler;

       /** The actual proxy instance wrapping the DMO proxy. */
       private Temporary mutableProxy;

       /**
        * For a master buffer, all explicit buffers created for it.
        * TODO: I think there is a memory leak hear, needs to be reviewed.
        */
       private final Set<Temporary> explicitBuffers = Collections.newSetFromMap(new IdentityHashMap<>());

       /**
        * Flag indicating that the {@link #loopDelete} is being executed, so the PKs can be reclaimed for the
        * record delete.
        */
       private boolean loopingDelete = false;

       /** Flag indicating is the buffer was deleted as a resource. */
       private boolean deleted = false;

       /**
        * Constructor. This class is only instantiated via the {@link #define}
        * factory method.  The implementation class associated with the given
        * <code>dmoIface</code> must have a constructor which accepts a multiplex
        * ID and must contain a <code>_multiplex</code> instance variable of type
        * <code>Integer</code>.
        *
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's API.  This interface MUST extend
        *          {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer is associated
        *          in the transaction manager whenever it is registered.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        */
       private <T extends TempTableBuffer> TemporaryBuffer(Class<?> def,
                                                           Class<T> dmoBufIface,
                                                           String variable,
                                                           boolean global,
                                                           UndoStateProvider undoable,
                                                           int blockDepth)
       {
          super(def, dmoBufIface, DatabaseManager.TEMP_TABLE_SCHEMA, variable, blockDepth);

          this.global = global;
          this.undoStateProvider = undoable;
          this.local = context.get();
       }

       /**
        * Defer error that has occurred during {@link #copyAllRows} processing.
        */
       public static void deferCopyError()
       {
          TransactionManager.deferError("copying", new ErrorConditionException("Copy all rows error"), true);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          boolean global)
       {
          return define(dmoBufIface, variable, null, global);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global)
       {
          return define(null, dmoBufIface, variable, legacyName, global);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<?> def,
                                                          Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             return DEF_BUFFER.timerWithReturn(() -> defineImpl(def, dmoBufIface, variable, legacyName, global));
          }
          else
          {
             return defineImpl(def, dmoBufIface, variable, legacyName, global);
          }
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       private static <T extends TempTableBuffer> T defineImpl(Class<?> def,
                                                               Class<T> dmoBufIface,
                                                               String variable,
                                                               String legacyName,
                                                               boolean global)
       {
          TemporaryBuffer buffer = makeBuffer(def, dmoBufIface, variable, global, new DefaultUndoState(true));

          Map<TemporaryBuffer, Object> domain = new WeakHashMap<>();
          domain.put(buffer, null);
          Map<TemporaryBuffer, Map<TemporaryBuffer, Object>> domains = buffer.local.domains;
          domains.put(buffer, domain);

          return makeMutable(dmoBufIface, createProxy(dmoBufIface, buffer, legacyName));
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          boolean global,
                                                          UndoStateProvider undoable)
       {
          return define(dmoBufIface, variable, null, global, undoable);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          <code>true</code> if changes made to records managed by this
        *          buffer can be undone;  <code>false</code> if such changes are
        *          committed immediately, such that they cannot be undone.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          boolean global,
                                                          boolean undoable)
       {
          return define(dmoBufIface, variable, null, global, new DefaultUndoState(undoable));
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          UndoStateProvider undoable)
       {
          return define(dmoBufIface, variable, legacyName, global, undoable, -1);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          <code>true</code> if changes made to records managed by this
        *          buffer can be undone;  <code>false</code> if such changes are
        *          committed immediately, such that they cannot be undone.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          boolean undoable)
       {
          return define(dmoBufIface, variable, legacyName, global, new DefaultUndoState(undoable), -1);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          <code>true</code> if changes made to records managed by this
        *          buffer can be undone;  <code>false</code> if such changes are
        *          committed immediately, such that they cannot be undone.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<?> def,
                                                          Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          boolean undoable)
       {
          return define(def, dmoBufIface, variable, legacyName, global, new DefaultUndoState(undoable), -1);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The buffer is permanently
        * associated with the temp table database. It will be registered with the transaction manager
        * under the specified variable name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a sub-interface of
        *          {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be associated in the
        *          transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          {@code true} if the buffer survives until end of context life;  {@code false} if it
        *          expires upon leaving the top level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or {@code -1} for the current scope.
        *
        * @return  An object which implements the specified interface, but which initially has no
        *          backing data record.
        *
        * @throws  IllegalArgumentException
        *          if {@code dmoIface} is not assignable from {@code Temporary}.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          UndoStateProvider undoable,
                                                          int blockDepth)
       {
          return define(null, dmoBufIface, variable, legacyName, global, undoable, blockDepth, true);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<?> def,
                                                          Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          UndoStateProvider undoable,
                                                          int blockDepth)
       {
          return define(def, dmoBufIface, variable, legacyName, global, undoable, blockDepth, true);
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        * @param   mutable
        *          Flag indicating if the buffer must be mutable.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       static <T extends TempTableBuffer> T define(Class<?> def,
                                                   Class<T> dmoBufIface,
                                                   String variable,
                                                   String legacyName,
                                                   boolean global,
                                                   UndoStateProvider undoable,
                                                   int blockDepth,
                                                   boolean mutable)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             return DEF_BUFFER.timerWithReturn(
                () -> defineImpl(def, dmoBufIface, variable, legacyName, global, undoable, blockDepth, mutable));
          }
          else
          {
             return defineImpl(def, dmoBufIface, variable, legacyName, global, undoable, blockDepth, mutable);
          }
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        * @param   mutable
        *          Flag indicating if the buffer must be mutable.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from
        *          <code>Temporary</code>.
        */
       private static <T extends TempTableBuffer> T defineImpl(Class<?> def,
                                                               Class<T> dmoBufIface,
                                                               String variable,
                                                               String legacyName,
                                                               boolean global,
                                                               UndoStateProvider undoable,
                                                               int blockDepth,
                                                               boolean mutable)
       {
          TemporaryBuffer buffer = makeBuffer(def, dmoBufIface, variable, global, undoable, blockDepth);

          Map<TemporaryBuffer, Object> domain = new WeakHashMap<>();
          domain.put(buffer, null);
          Map<TemporaryBuffer, Map<TemporaryBuffer, Object>> domains = buffer.local.domains;
          domains.put(buffer, domain);

          T proxy = createProxy(dmoBufIface, buffer, legacyName);

          return mutable ? makeMutable(dmoBufIface, proxy) : proxy;
       }

       /**
        * Define a new buffer which is based upon the specified interface. The
        * buffer is permanently associated with the temp table database. It will
        * be registered with the transaction manager under the specified variable
        * name.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          sub-interface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        *
        * @return  An object which implements the specified interface, but which
        *          initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>dmoIface</code> is not assignable from <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<T> dmoBufIface,
                                                          String variable,
                                                          String legacyName,
                                                          boolean global,
                                                          boolean undoable,
                                                          int blockDepth)
       {
          return define(dmoBufIface, variable, legacyName, global, new DefaultUndoState(undoable), blockDepth);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Temporary template, String variable)
       {
          return define(template, variable, null);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Temporary template,
                                                          String variable,
                                                          String legacyName)
       {
          return define(null, template, variable, legacyName, -1, false);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<?>  def,
                                                          Temporary template,
                                                          String    variable,
                                                          String    legacyName)
       {
          return define(def, template, variable, legacyName, -1, false);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.  This is used as an
        * analog to the DEFINE BUFFER statement when defining a buffer for a temp-table or work-table.
        * The DMO interface and global setting of the master buffer are used for the new buffer.
        * This buffer will have the same multiplex ID as the master buffer, once it is assigned to
        * either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with a temporary buffer
        *          that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be associated in the
        *          transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   shared
        *          When <code>true</code>, this flag indicates we are defining a new shared buffer.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Temporary template,
                                                          String variable,
                                                          String legacyName,
                                                          boolean shared)
       {
          return define(null, template, variable, legacyName, -1, shared);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Temporary template,
                                                          String variable,
                                                          int blockDepth)
       {
          return define(null, template, variable, null, blockDepth, false);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Temporary template,
                                                          String variable,
                                                          String legacyName,
                                                          int blockDepth)
       {
          return define(null, template, variable, legacyName, blockDepth, false);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        * @param   shared
        *          When <code>true</code>, this flag indicates we are defining a new shared buffer.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       public static <T extends TempTableBuffer> T define(Class<?>  def,
                                                          Temporary template,
                                                          String variable,
                                                          String legacyName,
                                                          int blockDepth,
                                                          boolean shared)
       {
          return define(def, template, variable, legacyName, blockDepth, shared, true);
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        * @param   shared
        *          When <code>true</code>, this flag indicates we are defining a new shared buffer.
        * @param   mutable
        *          Flag indicating if the buffer must be mutable.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       @SuppressWarnings("unchecked")
       static <T extends TempTableBuffer> T define(Class<?>  def,
                                                   Temporary template,
                                                   String    variable,
                                                   String    legacyName,
                                                   int       blockDepth,
                                                   boolean   shared,
                                                   boolean   mutable)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             return DEF_BUFFER.timerWithReturn(
                () -> defineImpl(def, template, variable, legacyName, blockDepth, shared, mutable));
          }
          else
          {
             return defineImpl(def, template, variable, legacyName, blockDepth, shared, mutable);
          }
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer.
        * This is used as an analog to the DEFINE BUFFER statement when defining
        * a buffer for a temp-table or work-table.  The DMO interface and global
        * setting of the master buffer are used for the new buffer.  This buffer
        * will have the same multiplex ID as the master buffer, once it is
        * assigned to either buffer.
        *
        * @param   <T>
        *          Type of constructed instance.
        * @param   def
        *          The currently executing OE class or external program.
        * @param   template
        *          An instance of <code>Temporary</code> which is associated with
        *          a temporary buffer that serves as a template for the new
        *          buffer.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   legacyName
        *          Legacy name of the buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or <code>-1</code> for the current scope.
        * @param   shared
        *          When <code>true</code>, this flag indicates we are defining a new shared buffer.
        * @param   mutable
        *          Flag indicating if the buffer must be mutable.
        *
        * @return  An object which implements the DMO interface of the master
        *          buffer, but which initially has no backing data record.
        *
        * @throws  IllegalArgumentException
        *          if <code>master</code>'s DMO interface is not assignable from
        *          <code>Temporary</code>.
        */
       @SuppressWarnings("unchecked")
       private static <T extends TempTableBuffer> T defineImpl(Class<?>  def,
                                                               Temporary template,
                                                               String    variable,
                                                               String    legacyName,
                                                               int       blockDepth,
                                                               boolean   shared,
                                                               boolean   mutable)
       {
          // an explicit buffer must always be created using as master the buffer as it exists on
          // conversion; after that, if the master is bound, this will be bound, too.
          TemporaryBuffer m = (TemporaryBuffer) ((BufferReference) template).definition();

          TemporaryBuffer masterBuf = (m.master == null) ? m : m.master;
          Class<T> dmoBufIface = (Class<T>) masterBuf.getDMOBufInterface();
          TemporaryBuffer buffer = makeBuffer(def,
                                              dmoBufIface,
                                              variable,
                                              masterBuf.global,
                                              masterBuf.undoStateProvider,
                                              blockDepth);
          buffer.master = masterBuf;
          buffer.referenceOnly = masterBuf.referenceOnly;
          buffer.tempTableRef = masterBuf.tempTableRef; // inherit this, as is directly accessed
          buffer.multiplexID = masterBuf.multiplexID;

          ProcedureManager.ProcedureHelper pm = buffer.getProcedureManager();
          Map<TemporaryBuffer, Map<TemporaryBuffer, Object>> domains = buffer.local.domains;
          Map<TemporaryBuffer, Object> domain = domains.get(masterBuf);
          domain.put(buffer, null);

          T proxy = createProxy(dmoBufIface, buffer, legacyName);

          if (shared)
          {
             SharedVariableManager.addBuffer(ScopeLevel.NEXT, variable, proxy);
          }

          // register this in the master buffer's explicit buffer set

          if (!mutable)
          {
             return proxy;
          }

          proxy = makeMutable(dmoBufIface, proxy);
          buffer.mutableProxy = proxy;
          buffer.master.explicitBuffers.add(proxy);

          // bind it if the master is bound
          TemporaryBuffer boundMaster = (TemporaryBuffer) get(template);
          if (boundMaster != m)
          {
             Temporary newBuf;
             try
             {
                pm.disablePendingResources();
                newBuf = TemporaryBuffer.define(def,
                                                (Temporary) boundMaster.getDMOProxy(),
                                                buffer.getDMOAlias(),
                                                buffer.getLegacyName(),
                                                -1,
                                                false,
                                                false);
                boundMaster.explicitBuffers.remove(newBuf);
             }
             finally
             {
                pm.enablePendingResources();
             }

             TemporaryBuffer tbuf = (TemporaryBuffer) ((BufferReference) buffer).buffer();

             buffer.mutableHandler.bind(newBuf, true);
             pm.executeOnReturn(() -> {
                buffer.mutableHandler.unbind();
                tbuf._setDynamic();

                ((BufferImpl) newBuf).delete();
             }, WeightFactor.LAST);
          }

          return proxy;
       }

       /**
        * Define a new buffer which is based upon a &quot;master&quot; buffer
        * stored in the {@link com.goldencode.p2j.util.SharedVariableManager
        * SharedVariableManager}.  This is used as an analog to the DEFINE SHARED
        * TEMP-TABLE statement when defining a buffer for a shared temp-table or
        * work-table which has been established elsewhere.  The DMO interface,
        * global, and undoability settings of the master buffer are used for the
        * new buffer.  This buffer will have the same multiplex ID as the master
        * buffer, which should have been assigned by an enclosing scope by the
        * time this method is invoked.
        *
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager and shared variable
        *          manager.
        * @param   expected
        *          The expected DMO type of the existing master temp-table.
        *
        * @return  An object which is bound to the master buffer.
        *
        * @throws  IllegalArgumentException
        *          if {@code master}'s DMO interface is not assignable from {@code Temporary}.
        */
       public static Temporary useShared(String variable, Class<? extends Temporary> expected)
       {
          // TODO: The error message should contain the legacy name of the TEMP-TABLE, not the java variable name!
          //       The chances are the lookup might also fail for same reason (the local name for a temp-table
          //       might different in different external procedures if local name collisions occur (?)).
          //       Maybe use the legacy name also for the procedure name, also present in error message?

          Temporary master = SharedVariableManager.lookupTempTable(variable, expected);

          // define a secondary buffer for the master
          String masterVar = ((BufferReference) master).buffer().getDMOAlias();
          Temporary masterBuffer = define(master, masterVar);

          boolean global = ((BufferReference) masterBuffer).definition().isGlobal();
          Temporary child = define((Class) expected, variable, global);
          TemporaryBuffer childBuffer = (TemporaryBuffer) ((BufferReference) child).buffer();
          ReferenceProxy childHandler = childBuffer.mutableHandler;
          childHandler.bind(masterBuffer, true);

          return child;
       }

       /**
        * Create a buffer which is bound to the master buffer, for use in a child shared buffer case.
        *
        * @param   master
        *          The master buffer.
        * @param   expected
        *          The child's buffer DMO interface.
        * @param   name
        *          The child buffer's name.
        *
        * @return  An object which is bound to the master buffer.
        */
       public static Temporary useShared(Temporary master, Class<?> expected, String name)
       {
          boolean global = ((BufferReference) master).definition().isGlobal();
          Temporary child = define((Class) expected, name, global);

          TemporaryBuffer childBuffer = (TemporaryBuffer) ((BufferReference) child).buffer();
          ReferenceProxy childHandler = childBuffer.mutableHandler;
          childHandler.bind(master, false);

          return child;
       }

       /**
        * Associate two temp tables with one another, such that all relevant
        * records can be copied between them at prescribed times.
        *
        * @param    src
        *           <code>TableParameter</code> containing reference to the source
        *           temporary buffer or result set.
        * @param    dstDMO
        *           DMO instance returned by a previous call to {@link #define} on
        *           the destination temporary buffer.
        *
        * @throws   ErrorConditionException
        *           if a recoverable error occurs flushing the source buffer in
        *           preparation for an input copy or while copying records.
        * @throws   RuntimeException
        *           if an unrecoverable error occurs invoking methods on the
        *           underlying DMOs to get or set data values.
        */
       public static void associate(TableParameter src, Temporary dstDMO)
       {
          associate(src, dstDMO, ParameterOption.NONE);
       }

       /**
        * Associate two temp tables with one another, such that all relevant
        * records can be copied between them at prescribed times.
        *
        * @param    src
        *           <code>TableParameter</code> containing reference to the source
        *           temporary buffer or result set.
        * @param    dstDMO
        *           DMO instance returned by a previous call to {@link #define} on
        *           the destination temporary buffer.
        * @param    input
        *           if <code>true</code>, records are copied from source table to
        *           destination table immediately upon invocation of this method,
        *           otherwise they are not.
        * @param    output
        *           if <code>true</code>, records are copied from destination
        *           table back to source table when the current transaction or
        *           subtransaction is committed;  otherwise they are not.
        *
        * @throws   ErrorConditionException
        *           if a recoverable error occurs flushing the source buffer in
        *           preparation for an input copy or while copying records.
        * @throws   RuntimeException
        *           if an unrecoverable error occurs invoking methods on the
        *           underlying DMOs to get or set data values.
        */
       public static void associate(TableParameter src,
                                    Temporary      dstDMO,
                                    boolean        input,
                                    boolean        output)
       {
          associate(src, dstDMO, input, output, ParameterOption.NONE);
       }

       /**
        * Associate two temp tables with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param    src
        *           {@code TableParameter} containing reference to the source temporary buffer or
        *           result set.
        * @param    dstDMO
        *           DMO instance returned by a previous call to {@link #define} on the destination
        *           temporary buffer.
        * @param    input
        *           If {@code true}, records are copied from source table to destination table
        *           immediately upon invocation of this method, otherwise they are not.
        * @param    output
        *           If {@code true}, records are copied from destination table back to source table
        *           when the current transaction or subtransaction is committed;  otherwise they are
        *           not.
        * @param    option
        *           Parameter passing mode APPEND, BY-VALUE, BIND or BY-REFERENCE option.
        *
        * @throws   ErrorConditionException
        *           if a recoverable error occurs flushing the source buffer in preparation for an
        *           input copy or while copying records.
        * @throws   RuntimeException
        *           if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
        *           set data values.
        */
       public static void associate(TableParameter  src,
                                    Temporary       dstDMO,
                                    boolean         input,
                                    boolean         output,
                                    ParameterOption option)
       {
          associate(src, dstDMO, input, output, EnumSet.of(option));
       }

       /**
        * Associate two temp tables with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param    src
        *           {@code TableParameter} containing reference to the source temporary buffer or
        *           result set.
        * @param    dstDMO
        *           DMO instance returned by a previous call to {@link #define} on the destination
        *           temporary buffer.
        * @param    input
        *           If {@code true}, records are copied from source table to destination table
        *           immediately upon invocation of this method, otherwise they are not.
        * @param    output
        *           If {@code true}, records are copied from destination table back to source table
        *           when the current transaction or subtransaction is committed;  otherwise they are
        *           not.
        * @param    options
        *           Parameter passing mode APPEND, BY-VALUE, BIND or BY-REFERENCE options.
        *
        * @throws   ErrorConditionException
        *           if a recoverable error occurs flushing the source buffer in preparation for an
        *           input copy or while copying records.
        * @throws   RuntimeException
        *           if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
        *           set data values.
        */
       public static void associate(TableParameter  src,
                                    Temporary       dstDMO,
                                    boolean         input,
                                    boolean         output,
                                    EnumSet<ParameterOption> options)
       {
          if (src.isBind() && options.contains(ParameterOption.BIND) && output && !input)
          {
             // reverse the source and destination
             if (src.isTableHandle())
             {
                // TODO: what?
             }
             else
             {
                boolean refOnly = ((TemporaryBuffer) get(src.getTable())).referenceOnly;
                if (refOnly)
                {
                   Temporary aux = src.getTable();
                   src.setTable(dstDMO);
                   dstDMO = aux;
                }
             }
          }
          src.setInputMode(input);
          src.setOutputMode(output);

          associate(src, dstDMO, options);
       }

       /**
        * Associate two temp tables with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   src
        *          {@code TableParameter} containing reference to the source temporary buffer or
        *          result set.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on the destination
        *          temporary buffer.
        * @param   option
        *          Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs flushing the source buffer in preparation for an
        *          input copy or while copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
        *          set data values.
        */
       public static void associate(final TableParameter src, Temporary dstDMO, ParameterOption option)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             STATIC_PARAM.timer(() -> associateImpl(src, dstDMO, EnumSet.of(option)));
          }
          else
          {
             associateImpl(src, dstDMO, EnumSet.of(option));
          }
       }

       /**
        * Associate two temp tables with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   src
        *          {@code TableParameter} containing reference to the source temporary buffer or
        *          result set.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on the destination
        *          temporary buffer.
        * @param   options
        *          Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs flushing the source buffer in preparation for an
        *          input copy or while copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
        *          set data values.
        */
       public static void associate(TableParameter src, Temporary dstDMO, EnumSet<ParameterOption> options)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             STATIC_PARAM.timer(() -> associateImpl(src, dstDMO, options));
          }
          else
          {
             associateImpl(src, dstDMO, options);
          }
       }

       /**
        * Associate two temp tables with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   src
        *          {@code TableParameter} containing reference to the source temporary buffer or
        *          result set.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on the destination
        *          temporary buffer.
        * @param   options
        *          Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs flushing the source buffer in preparation for an
        *          input copy or while copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
        *          set data values.
        */
       private static void associateImpl(TableParameter src, Temporary dstDMO, EnumSet<ParameterOption> options)
       {
          if (src.getTable() == (Temporary) ((BufferImpl) dstDMO).ref())
          {
             // same reference, don't do anything
             return;
          }

          boolean input  = src.isInputMode();
          boolean output = src.isOutputMode();
          boolean append = options.contains(ParameterOption.APPEND);

          if (!(input || output))
          {
             throw new IllegalStateException("Parameter mode (INPUT/OUTPUT) was not specified!");
          }

          TemporaryBuffer buffer = (TemporaryBuffer) get(dstDMO);

          BufferManager.registerScopeable(buffer.bufferManager);

          ProcedureManager.ProcedureHelper pm = buffer.getProcedureManager();

          if (buffer.referenceOnly && !(src.isByReference() || src.isBind()))
          {
             Object referent = pm._thisProcedure();
             String pname = pm.getRelativeName(referent);
             String bufName = ((Buffer) src.getTable()).name().toJavaType();

             String msg = "%s BIND or BY-REFERENCE modifier required on RUN, FUNCTION or METHOD " +
                          "invocation parameter when called TABLE or DATASET parameter %s is " +
                          "REFERENCE-ONLY and is still not bound";
             msg = String.format(msg, pname, bufName);

             if (buffer.getTxHelper().isSuppressError())
             {
                ErrorManager.addError(13012, msg, true, false);

                throw new ErrorConditionException(new NumberedException(msg + ". (13012)", 13012));
             }
             else
             {
                ErrorManager.recordOrThrowError(13012, msg, false);

                // TODO: this doesn't raise an ERROR at the caller ...
             }

             return;
          }

          if (src.isRemoteParameter()  && !src.getResultSet().isReceivedSchema())
          {
             RecordBuffer rb = ((BufferReference) dstDMO).buffer();
             DmoMeta meta = rb.getDmoInfo();
             Iterator<Property> props = meta.getFields(false);
             List<PropertyDefinition> pdl = new ArrayList<>();
             props.forEachRemaining(p -> pdl.add(new PropertyDefinition(p)));
             src.getResultSet().setProperties(pdl);
          }

          boolean bind = src.isBind() || options.contains(ParameterOption.BIND);

          // a BIND at the param or argument has effect only if either the argument has BY-REFERENCE or the target
          // is REFERENCE-ONLY
          if (!(src.isByReference()                                                                 ||
                (src.getTable() != null &&
                 ((TemporaryBuffer) ((BufferReference) src.getTable()).definition()).referenceOnly) ||
                ((TemporaryBuffer) ((BufferReference) dstDMO).definition()).referenceOnly))
          {
             bind = false;
          }

          if (src.isByReference() || bind || (!src.isRemoteParameter() && buffer.referenceOnly))
          {
             Temporary srcTable = src.getTable();
             if (srcTable == null)
             {
                if (!src.isRemoteParameter())
                {
                   // this is a TABLE-HANDLE!
                   handle hsrc = src.getTableHandle();
                   if (hsrc._isValid())
                   {
                      src.setTable((Temporary) hsrc.getResource());
                   }
                   else
                   {

                      hsrc.assign(((BufferReference) dstDMO).buffer().tableHandle());
                      src.setTable(dstDMO);
                   }
                }
                else
                {
                   // TODO: remote table-handle param ??
                }
             }
             else
             {
                if (output && ((TemporaryBuffer) ((BufferReference) srcTable).buffer()).referenceOnly)
                {
                   // switch source and dest
                   Temporary old = srcTable;
                   srcTable = dstDMO;
                   dstDMO = old;
                }

                // save the reference, will be needed for bind later
                TemporaryBuffer dstBufferTarget = (TemporaryBuffer) ((BufferReference) dstDMO).definition();

                // find the default buffer and perform the binding of the before-buffer, if it exists
                TemporaryBuffer dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).definition();
                dstDMO = (Temporary) dstBuf.getParentTable().defaultBufferHandleNative();
                dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).buffer();
                boolean dstReferenceOnly = dstBuf.referenceOnly;

                // do not re-BIND if a REFERENCE-ONLY table is already bound to the same target table
                TemporaryBuffer srcDefaultBuf = ((TemporaryBuffer) ((BufferReference) srcTable).buffer());
                if (srcDefaultBuf.mutableHandler != null && srcDefaultBuf.mutableHandler.bound != null)
                {
                   // if the source is bound, then get the 'real source'.  This can happen because static DATASETs
                   // work with the 'definition' and not the actual ReferenceProxy instance.
                   srcTable = srcDefaultBuf.mutableHandler.bound;
                   srcDefaultBuf = srcDefaultBuf.mutableHandler.boundBuffer;
                }
                srcDefaultBuf = (TemporaryBuffer) ((BufferReference) srcDefaultBuf.getParentTable()
                                                                                  .defaultBufferHandleNative())
                                                                                  .buffer();
                if (dstBuf.mutableHandler.boundBuffer == null)
                {
                   // this is the very first bind
                   handle hSrcBefore = ((Buffer) srcTable).beforeBuffer();
                   if (!hSrcBefore.isUnknown())
                   {
                      // associate the before buffer, too!
                      handle hDstBefore = ((Buffer) dstDMO).beforeBuffer();
                      if (hDstBefore.isUnknown())
                      {
                         // TODO: error?
                      }
                      else
                      {
                         TableParameter beforeParam = new TableParameter((Temporary) hSrcBefore.getResource(),
                                                                         src.getParameterOptions(),
                                                                         input,
                                                                         output);
                         associate(beforeParam, (Temporary) hDstBefore.getResource(), options);
                      }
                   }
                }

                {
                   // CA: this test is disabled, as it regresses a BIND case for OUTPUT BIND where the caller is
                   // REFERENCE-ONLY
                   /*
                   if (bind && (srcBuf.referenceOnly || dstBuf.referenceOnly))
                   {
                      handle procHandle = ProcedureManager.thisProcedure();
                      String procName = ((PersistentProcedure) procHandle.getResource()).name().toJavaType();
                      ErrorManager.recordOrThrowError(13009, procName, dstBuf.getLegacyName());
                      // <program name> BIND modifier not allowed for cases where neither the caller nor the
                      // called TABLE or DATASET parameter <parameter-name> has been defined REFERENCE-ONLY and
                      // neither caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE. (13009)
                   }
                   else
                   */
                   {
                      // go through all explicit buffers in the dest table, including the default buffer
                      Set<Temporary> copy = new HashSet<>(dstBuf.explicitBuffers);
                      copy.add(dstDMO);
                      for (Temporary dmo : copy)
                      {
                         TemporaryBuffer exBuf = (TemporaryBuffer) ((BufferReference) dmo).definition();

                         // if the buffer is not already bound, then:
                         // 1. the DMO target: this and only this will be explicitly bound
                         // 2. all other explicit or default buffer will have a new buffer created

                         if (exBuf.mutableHandler.boundBuffer != null)
                         {
                            // skip the buffer which is bound from a previous call
                            continue;
                         }

                         if (exBuf == dstBufferTarget)
                         {
                            Temporary oldBound = exBuf.mutableHandler.bind((Temporary) ((BufferImpl) srcTable).ref());

                            // CA: added back the !bind test, as the binding needs to be rolledback on procedure exit,
                            // in non-bind cases
                            if (!bind || !dstReferenceOnly)
                            {
                               // TODO: can a future BIND call veto any unbind from an existing call on the stack?
                               TemporaryBuffer finalDstBuf = exBuf;
                               pm.executeOnReturn(() -> {
                                  finalDstBuf.mutableHandler.unbind();

                                  if (oldBound != null)
                                  {
                                     finalDstBuf.mutableHandler.bind(oldBound);
                                  }
                               }, WeightFactor.LAST);
                            }

                         }
                         else
                         {
                            // create a new buffer
                            Temporary newBuf;
                            try
                            {
                               pm.disablePendingResources();
                               newBuf = TemporaryBuffer.define(null,
                                                               srcTable,
                                                               exBuf.getDMOAlias(),
                                                               exBuf.getLegacyName(),
                                                               -1,
                                                               false,
                                                               false);
                               exBuf.explicitBuffers.remove(newBuf);
                            }
                            finally
                            {
                               pm.enablePendingResources();
                            }

                            Temporary oldExBound = exBuf.mutableHandler.bind(newBuf, true);
                            TemporaryBuffer tbuf = (TemporaryBuffer) ((BufferReference) newBuf).buffer();
                            // open scope for this new buffer. Bound buffers go to global scope, the other local
                            tbuf.openScopeAt(bind ? 0 : tbuf.bufferManager.getOpenBufferScopes() - 1);

                            if (!bind || !dstReferenceOnly)
                            {
                               pm.executeOnReturn(() -> {
                                  exBuf.mutableHandler.unbind();

                                  if (oldExBound != null)
                                  {
                                     exBuf.mutableHandler.bind(oldExBound);
                                  }

                                  tbuf._setDynamic();

                                  ((BufferImpl) newBuf).delete();
                               }, WeightFactor.LAST);
                            }
                         }
                      }
                   }
                }

                return;
             }
          }

          // same reference, nothing to do
          if (src.getTableHandle() != null && src.getTableHandle().equals(buffer.tableHandle()))
          {
             // by-ref/ref-only still have work to do related to keeping the mutable buffers in sync.
             return;
          }

          if (input)
          {
             if (!src.isRemoteParameter())
             {
                Temporary srcDMO = src.getTable();

                if (srcDMO != null)
                {
                   TemporaryBuffer srcBuf = (TemporaryBuffer) ((BufferReference) srcDMO).buffer();
                   Persistence.Context pc = srcBuf.persistenceContext;
                   boolean inTx = false;

                   try
                   {
                      if (srcBuf.isTransient() && !srcBuf.bufferManager.isTransaction())
                      {
                         inTx = pc.beginTransaction(null);
                      }

                      srcBuf.flush();

                      if (inTx)
                      {
                         pc.commit();
                      }
                   }
                   catch (ValidationException | PersistenceException exc)
                   {
                      if (inTx)
                      {
                         try
                         {
                            pc.rollback(true);
                         }
                         catch (PersistenceException pe)
                         {
                            if (LOG.isLoggable(Level.WARNING))
                            {
                               LOG.log(Level.WARNING, "Error flushing source temp-table buffer", exc);
                            }
                         }
                      }

                      ErrorManager.recordOrThrowError(exc);
                   }

                   rowid srcRec = (buffer.referenceOnly || src.isByReference()) && srcBuf.isAvailable()
                                     ? srcBuf.rowID()
                                     : null;

                   boolean res = copyAllRows(srcRec, srcDMO, dstDMO, append, CopyTableMode.INPUT_PARAM_MODE);
                   if (!res)
                   {
                      BlockManager.returnError();
                   }
                }
             }
             else if (src.getResultSet().getXmlTable() != null)
             {
                String xmlTable = src.getResultSet().getXmlTable();
                readFromXml(xmlTable, append, ((Buffer) dstDMO).tableHandle());
             }
             else
             {
                insertAllRows(src.getResultSet(),
                              dstDMO,
                              append,
                              CopyTableMode.INPUT_PARAM_MODE,
                              src.getParameterIndex());
             }
          }

          if (output)
          {
             // Set the destination DMO if output copy is required.  This is
             // actually the source DMO passed to this method, since the direction
             // of the copy is reversed on output.
             TemporaryBuffer dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).buffer();
             if (dstBuf.outputParameters == null)
             {
                dstBuf.outputParameters = Collections.newSetFromMap(new IdentityHashMap<>());
             }
             dstBuf.outputParameters.add(src);

             OutputTableCopier copier = new OutputTableCopier(dstBuf, src);
             pm.executeOnReturn(copier::finished, copier.weight());
          }
       }

       /**
        * Creates a dynamic temporary table for the given temp table handle. Intended to handle
        * conversion of DEFINE PARAMETER TABLE-HANDLE.
        *
        * @param    src
        *           <code>TableParameter</code> containing reference to the source table.
        * @param    target
        *           Handle for storing the target table.
        */
       public static void createDynamicTable(TableParameter src, handle target)
       {
          createDynamicTable(src, target, ParameterOption.NONE);
       }

       /**
        * Creates a dynamic temporary table for the given temp table handle. Intended to handle
        * conversion of DEFINE PARAMETER TABLE-HANDLE.
        *
        * @param    src
        *           <code>TableParameter</code> containing reference to the source table.
        * @param    target
        *           Handle for storing the target table.
        * @param    input
        *           if <code>true</code>, records are copied from source table to
        *           destination table immediately upon invocation of this method,
        *           otherwise they are not.
        * @param    output
        *           if <code>true</code>, records are copied from destination
        *           table back to source table when the current transaction or
        *           subtransaction is committed;  otherwise they are not.
        */
       public static void createDynamicTable(TableParameter src,
                                             handle         target,
                                             boolean        input,
                                             boolean        output)
       {
          createDynamicTable(src, target, input, output, ParameterOption.NONE);
       }

       /**
        * Creates a dynamic temporary table for the given temp table handle. Intended to handle
        * conversion of DEFINE PARAMETER TABLE-HANDLE.
        *
        * @param    src
        *           {@code TableParameter} containing reference to the source table.
        * @param    target
        *           Handle for storing the target table.
        * @param    input
        *           If {@code true}, records are copied from source table to destination table
        *           immediately upon invocation of this method, otherwise they are not.
        * @param    output
        *           If {@code true}, records are copied from destination table back to source table
        *           when the current transaction or subtransaction is committed;  otherwise they are
        *           not.
        * @param    option
        *           Parameter passing mode APPEND, BY-VALUE, BIND or BY-REFERENCE option.
        */
       public static void createDynamicTable(TableParameter  src,
                                             handle          target,
                                             boolean         input,
                                             boolean         output,
                                             ParameterOption option)
       {
          src.setInputMode(input);
          src.setOutputMode(output);

          createDynamicTable(src, target, option);
       }

       /**
        * Creates a dynamic temporary table for the given temp table handle. Intended to handle
        * conversion of DEFINE PARAMETER TABLE-HANDLE.
        *
        * @param    src
        *           {@code TableParameter} containing reference to the source table.
        * @param    target
        *           Handle for storing the target table.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public static void createDynamicTable(TableParameter src, handle target, ParameterOption option)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             DYN_PARAM.timer(() -> createDynamicTableImpl(src, target, option));
          }
          else
          {
             createDynamicTableImpl(src, target, option);
          }
       }

       /**
        * Creates a dynamic temporary table for the given temp table handle. Intended to handle
        * conversion of DEFINE PARAMETER TABLE-HANDLE.
        *
        * @param    src
        *           {@code TableParameter} containing reference to the source table.
        * @param    target
        *           Handle for storing the target table.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       private static void createDynamicTableImpl(TableParameter src, handle target, ParameterOption option)
       {
          boolean input  = src.isInputMode();
          boolean output = src.isOutputMode();
          boolean append = (option == ParameterOption.APPEND);

          if (!(input || output))
          {
             throw new IllegalStateException("Parameter mode (INPUT/OUTPUT) was not specified!");
          }

          BufferManager bMgr = BufferManager.get();
          BufferManager.registerScopeable(bMgr);
          ProcedureManager.ProcedureHelper pm = bMgr.getProcedureHelper();

          if (output)
          {
             TransactionManager.registerCurrent(target);
          }

          if (src.isByReference() || src.isBind())
          {
             target.assign(src.getTableHandle());

             if (target._isValid())
             {
                if (target.getResource() instanceof TempTableBuilder)
                {
                   TempTableBuilder ttb = (TempTableBuilder) target.getResource();
                   boolean old = ttb.isPostponedDelete();
                   ttb.setPostponedDelete(true);
                   pm.executeOnReturn(() -> ttb.setPostponedDelete(old), WeightFactor.LAST);
                }
             }
             else if (option == ParameterOption.BIND)
             {
                StaticTempTable table = (StaticTempTable) target.getResource();
                TemporaryBuffer targetBuffer = (TemporaryBuffer) ((BufferImpl) table.defaultBufferHandleNative()).buffer();
                if (targetBuffer.referenceOnly)
                {
                   // BIND is assumed
                   pm.executeOnReturn(() ->
                   {
                      // if target is still valid, then bind it to the argument
                      if (target._isValid())
                      {
                         TempTable tt = (TempTable) target.getResource();
                         BufferImpl defBuf = (BufferImpl) tt.defaultBufferHandleNative();
                         targetBuffer.bind((TemporaryBuffer) defBuf.buffer());
                      }
            	   }, WeightFactor.LAST);
            	}
             }
             else if (output)
             {
                OutputTableHandleCopier copier = new OutputTableHandleCopier(src, target);
                pm.executeOnReturn(copier::finished, copier.weight());
                bMgr.registerOutputTableHandleCopier(copier);
             }

             return;
          }

          if (input || src.isRemoteParameter())
          {
             handle srcHandle = src.getTableHandle();
             if (srcHandle._isValid())
             {
                TempTableBuilder dstTable = new TempTableBuilder();
                logical res = dstTable.copyTempTable(srcHandle, append, false, false, "");
                if (!res.booleanValue())
                {
                   BlockManager.returnError();
                }
                target.assign(dstTable);
             }
             else if (src.getResultSet() != null && src.getResultSet().getXmlTable() != null)
             {
                String xmlTable = src.getResultSet().getXmlTable();
                readFromXml(xmlTable, append, target);
             }
             else
             {
                TableWrapper resultSet = src.getResultSet();
                if (resultSet != null && resultSet.isValid() && resultSet.getTableName() != null)
                {
                   if (!resultSet.isReceivedSchema())
                   {
                      // A NO-SCHEMA-MARSHAL table cannot be used as a parameter where the receiving side
                      // does not have a pre-prepared schema
                      ErrorManager.recordOrThrowError(12323);
                   }

                   TempTableBuilder builder = new TempTableBuilder();
                   builder.createFromTableWrapper(resultSet, src.getParameterIndex());
                   target.assign(builder);
                }
             }
          }

          if (output)
          {
             OutputTableHandleCopier copier = new OutputTableHandleCopier(src, target);
             pm.executeOnReturn(copier::finished, copier.weight());
             bMgr.registerOutputTableHandleCopier(copier);
          }
       }

       /**
        * Get default buffer for a temporary table.
        *
        * @param  tempTableHandle
        *         Handle to a temporary table.
        *
        * @return see above.
        */
       public static Temporary getDefaultBuffer(handle tempTableHandle)
       {
          if (!tempTableHandle._isValid())
          {
             return null;
          }

          String msg = null;
          switch (((HandleResource) tempTableHandle.getResource()).type().toUpperCase())
          {
             case LegacyResource.TEMP_TABLE:
                // this is normally expected and will continue without raising errors
                break;
             case LegacyResource.BUFFER:
                msg = "BUFFER handle used where TEMP-TABLE handle parameter needed";
                ErrorManager.recordOrShowError(9067, msg, true, false);
                return null;
             default:
                msg = "TEMP-TABLE handle parameter requires valid and prepared handle";
                ErrorManager.recordOrShowError(9068, msg, true, false);
                return null;
          }

          TempTable tempTable = (TempTable) tempTableHandle.getResource();
          return (Temporary) tempTable.defaultBufferHandleNative();
       }

       /**
        * Get the DMO which represents the database record currently held in the buffer.
        *
        * @return DMO for the current record, or {@code null} if the buffer currently is empty.
        */
       @Override
       public TempRecord getCurrentRecord()
       {
          return (TempRecord) super.getCurrentRecord();
       }

       /**
        * Get an iterator on the names of all temp tables which currently exist
        * in the current context.
        *
        * @return  Iterator on active table names.
        */
       public static Iterator<String> activeTables()
       {
          return context.get().activeTables.iterator();
       }

       /**
        * Report the number of temp tables which currently exist in the current context.
        *
        * @return  Number of temp tables which have been created for the current context and not yet dropped.
        */
       public static int getTableCount()
       {
          Context ctx = null;
          try
          {
             ctx = TemporaryBuffer.context.get(false);
          }
          catch (ContextLocalCleanupException e)
          {
             // do nothing, ctx stays null
          }

          return ctx == null ? 0 : ctx.getTableCount();
       }

       /**
        * Populate the temp table with records created using the specified result set.
        *
        * @param    srcResultSet
        *           The source result set.
        * @param    dstDMO
        *           DMO instance refering the destination DMO buffer.
        * @param    append
        *           <code>true</code> to append the data, <code>false</code> to remove all records
        *           first.
        * @param    errorMode
        *           Table copy mode, affects error handling.
        * @param    tableParameterIndex
        *           1-based index of the corresponding table parameter (in declaration of the
        *           function). Affects error handling.
        *
        * @return   <code>true</code> if copy was successful. <code>false</code> if an error occurs
        *           during copying records and error was recorder rather than thrown.
        *
        * @throws   ErrorConditionException
        *           if an error occurs during copying records.
        */
       public static boolean insertAllRows(TableWrapper srcResultSet,
                                           Temporary dstDMO,
                                           boolean append,
                                           CopyTableMode errorMode,
                                           int tableParameterIndex)
       {
          TemporaryBuffer dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).buffer();
          dstBuf.initialize();
          TempTable dstTempTable = dstBuf.getParentTable();
          TemporaryBuffer dstB4Buf = null;
          boolean processB4 = false;
          TempTable beforeTT = dstTempTable.getBeforeTableNative();
          if (beforeTT != null && beforeTT.valid())
          {
             Temporary dstB4DMO = (Temporary) beforeTT.defaultBufferHandleNative();
             dstB4Buf = (TemporaryBuffer) ((BufferReference) dstB4DMO).buffer();
             processB4 = true;
          }

          // get map of destination setter and getter methods, and extents, keyed by property name
          Map<String, Method> dstSetters = dstBuf.getDmoInfo().getPojoSetterMap();
          Map<String, Method> dstGetters = dstBuf.getDmoInfo().getPojoGetterMap();
          Map<String, Integer> dstExtents = dstBuf.getDmoInfo().getExtentMap();
          Map<String, Method> dstB4Setters = processB4 ? dstB4Buf.getDmoInfo().getPojoSetterMap() : null;
          Map<String, Method> dstB4Getters = processB4 ? dstB4Buf.getDmoInfo().getPojoGetterMap() : null;
          Map<String, Integer> dstB4Extents = processB4 ? dstB4Buf.getDmoInfo().getExtentMap() : null;

          ArrayList<PropertyDefinition> srcPropList = srcResultSet.getProperties();
          int srcPropListSize                  = srcPropList.size();
          String[] srcProperties               = new String[srcPropListSize];
          for (int i = 0; i < srcPropListSize; i++)
          {
             srcProperties[i] = srcPropList.get(i).getName();
          }

          String[] dstProperties = TempTableBuilder.getOrderedPropertyNames(dstTempTable);

          if (!srcResultSet.isFromJson() && srcProperties.length != dstProperties.length)
          {
             // property validation is done only if this is not from a web service call
             if (errorMode == CopyTableMode.INPUT_PARAM_MODE)
             {
                String errorText = String.format(ERROR_TABLE_MISMATCH_TEMPLATE,
                                                 tableParameterIndex,
                                                 dstTempTable.name().toStringMessage(),
                                                 dstProperties.length,
                                                 srcProperties.length);
                throw new InvalidParameterConditionException(
                   new NumberedException(errorText, 8030, false));
             }
             else if (errorMode == CopyTableMode.OUTPUT_TABLE_PARAM_MODE ||
                      errorMode == CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE)
             {
                String errorText;
                if (srcResultSet.isTableHandle())
                {
                   errorText = String.format(ERROR_TABLE_MISMATCH_TEMPLATE,
                                             tableParameterIndex,
                                             dstTempTable.name().toStringMessage(),
                                             dstProperties.length,
                                             srcProperties.length);
                }
                else
                {
                   errorText = String.format(ERROR_TABLE_MISMATCH_TEMPLATE,
                                             tableParameterIndex,
                                             srcResultSet.getTableName(),
                                             srcProperties.length,
                                             dstProperties.length);
                }
                throw new InvalidParameterConditionException(
                                                    new NumberedException(errorText, 8030, false));
             }
             else
             {
                throw new IllegalStateException("Wrong table copy error mode: " + errorMode);
             }
          }

          if (!srcResultSet.isFromJson())
          {
             for (int i = 0; i < srcProperties.length; i++)
             {
                PropertyDefinition property = srcPropList.get(i);
                String dstPropName = dstProperties[i];

                // compare data types
                Method dstGetter = dstGetters.get(dstPropName);
                ParmType srcType = ParmType.fromClass(property.getType());
                ParmType dstType = ParmType.fromClass(dstGetter.getReturnType());

                if (srcType == null || dstType == null || !srcType.equals(dstType))
                {
                   if (errorMode == CopyTableMode.INPUT_PARAM_MODE)
                   {
                      throw new InvalidParameterConditionException(
                                        new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                   }
                   else if (errorMode == CopyTableMode.OUTPUT_TABLE_PARAM_MODE ||
                            errorMode == CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE)
                   {
                      if (!srcResultSet.isTableHandle())
                      {
                         throw new InvalidParameterConditionException(
                               new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                      }
                      else
                      {
                         String errorText = String.format(ERROR_FIELD_TYPE_MISMATCH_TEMPLATE,
                                                          dstTempTable.name().toStringMessage(),
                                                          i + 1);
                         ErrorManager.displayError(8028, errorText, false);
                         throw new InvalidParameterConditionException(
                               new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                      }
                   }
                   else
                   {
                      throw new IllegalStateException("Wrong table copy error mode: " + errorMode);
                   }
                }

                // compare extent sizes
                Integer dstExtent = dstExtents.get(dstPropName);
                long srcExtentSize = property.getExtent();
                if (srcExtentSize < 1)
                {
                   srcExtentSize = 1;
                }

                long dstExtentSize = dstExtent == null ? 1 : dstExtent.intValue();

                if (srcExtentSize != dstExtentSize)
                {
                   if (errorMode == CopyTableMode.INPUT_PARAM_MODE)
                   {
                      throw new InvalidParameterConditionException(
                         new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                   }
                   else if (errorMode == CopyTableMode.OUTPUT_TABLE_PARAM_MODE ||
                            errorMode == CopyTableMode.OUTPUT_TABLE_HANDLE_PARAM_MODE)
                   {
                      if (!srcResultSet.isTableHandle())
                      {
                         throw new InvalidParameterConditionException(
                            new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                      }
                      else
                      {
                         String errorText = String.format(ERROR_EXTENT_MISMATCH_TEMPLATE,
                                                          dstTempTable.name().toStringMessage(),
                                                          i + 1);
                         ErrorManager.displayError(8029, errorText, false);
                         throw new InvalidParameterConditionException(
                            new NumberedException(ERROR_MISMATCHED_FIELDS, 12324, false));
                      }
                   }
                   else
                   {
                      throw new IllegalStateException("Wrong table copy error mode: " + errorMode);
                   }
                }
             }
          }

          Map<Long, TempRecord> dstNewIds = processB4 ? new HashMap<>() : null;
          Map<Long, TempRecord> dstB4NewIds = processB4 ? new HashMap<>() : null;

          Persistence persistence = dstBuf.getPersistence();
          boolean savedAny = false;
          boolean savedB4Any = false;
          int commit = 0;   // 0 = do nothing, 1 = commit, -1 = rollback
          try
          {
             commit = (persistence.beginTransaction(Persistence.TEMP_CTX) ? 1 : 0);

             // if not in input-output mode, first remove all records
             if (!append)
             {
                if (!dstBuf.forceLoopingDelete())
                {
                   dstBuf.removeRecords(null, null, null);
                   if (processB4)
                   {
                      dstB4Buf.removeRecords(null, null, null);
                   }
                }
                else
                {
                   dstBuf.loopDelete(null, null, null);
                   if (processB4)
                   {
                      dstB4Buf.loopDelete(null, null, null);
                   }
                }
             }

             savedAny = createRecords(srcResultSet,
                                      dstBuf,
                                      dstNewIds,
                                      dstProperties,
                                      dstSetters,
                                      dstExtents);

             if (processB4 && srcResultSet.getPeerDef() != null)
             {
                savedB4Any = createRecords(srcResultSet.getPeerDef(),
                                           dstB4Buf,
                                           dstB4NewIds,
                                           dstProperties,
                                           dstB4Setters,
                                           dstB4Extents);

                for (TempRecord dstB4Ttr : dstB4NewIds.values())
                {
                   TempRecord dstTtr = dstNewIds.get(dstB4Ttr._peerRowid());

                   if (dstTtr != null)
                   {
                      // remap the links; this can be null in case of deleted records (row-state = ROW_DELETED)
                      dstB4Ttr._peerRowid(dstTtr.primaryKey());
                      dstTtr._peerRowid(dstB4Ttr.primaryKey());
                   }
                }
             }

             if (dstNewIds != null)
             {
                for (TempRecord ttr : dstNewIds.values())
                {
                   persistence.save(ttr, ttr.primaryKey());
                }
             }

             if (dstB4NewIds != null)
             {
                for (TempRecord ttr : dstB4NewIds.values())
                {
                   persistence.save(ttr, ttr.primaryKey());
                }
             }
          }
          catch (PersistenceException exc)
          {
             commit *= -1;
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             ErrorManager.recordOrThrowError(exc);
          }
          catch (IllegalAccessException | InstantiationException exc)
          {
             commit *= -1;

             throw new RuntimeException("Error creating record", exc);
          }
          catch (InvocationTargetException exc)
          {
             commit *= -1;
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);

             throw new RuntimeException("Error creating record", exc);
          }
          finally
          {
             if (savedAny)
             {
                dstBuf.local.nonEmptyTableFlags.add(dstBuf.multiplexID);
             }
             if (savedB4Any)
             {
                dstB4Buf.local.nonEmptyTableFlags.add(dstB4Buf.multiplexID);
             }

             try
             {
                switch (commit)
                {
                   case 1:
                      persistence.commit(Persistence.TEMP_CTX);
                      break;
                   case -1:
                      persistence.rollback(Persistence.TEMP_CTX);
                      break;
                   case 0:
                      break;
                }
             }
             catch (PersistenceException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
                ErrorManager.recordOrThrowError(exc);
             }
          }

          return true;
       }

       /**
        * Create records from the given result set.
        *
        * @param    srcResultSet
        *           The source result set.
        * @param    dstBuf
        *           The destination DMO buffer.
        * @param    dstNewIds
        *           Map to save the new record IDs.  May be {@code null}, in which case the
        *           record is saved immediately; otherwise, the save will be postponed, to be able to
        *           cross-link with the before-table meta information.
        * @param    dstProperties
        *           The properties to save.
        * @param    dstSetters
        *           The property setters.
        * @param    dstExtents
        *           The property extents.
        *
        * @return   {@code true} if at least one record was added.
        */
       private static boolean createRecords(TableWrapper          srcResultSet,
                                            TemporaryBuffer       dstBuf,
                                            Map<Long, TempRecord> dstNewIds,
                                            String[]              dstProperties,
                                            Map<String, Method>   dstSetters,
                                            Map<String, Integer>  dstExtents)
       throws InstantiationException,
              IllegalAccessException,
              InvocationTargetException,
              PersistenceException
       {
          DatumAccess[] datums = new DatumAccess[dstProperties.length];
          for (int i = 0; i < dstProperties.length; i++)
          {
             String dstProp = dstProperties[i];

             DatumAccess da = dstBuf.getSetterDatums().get(dstProp);

             if (da == null)
             {
                Method dstSetter = dstSetters.get(dstProp);
                PropertyMeta dstMeta = dstBuf.getDmoInfo().getPropsBySetterMap().get(dstSetter);
                boolean dstAssignTrigger =
                         dstSetter.getReturnType() != Void.class &&
                         dstMeta.getOriginalExtent() == 0        &&
                         dstBuf.getTriggerTracker() != null      &&
                         dstBuf.getTriggerTracker().isTriggerEnabled(DatabaseEventType.ASSIGN,
                                                                     dstProp,
                                                                     dstBuf.getLogicalDatabase());
                da = new DatumAccess(dstProp,
                                     null,
                                     dstSetter,
                                     dstExtents.get(dstProp),
                                     dstMeta,
                                     dstBuf.isPropertyIndexed(dstMeta.getOffset()),
                                     dstAssignTrigger);
                dstBuf.getSetterDatums().put(dstProp, da);
             }

             datums[i] = da;
          }

          boolean savedAny = false;
          Persistence persistence = dstBuf.getPersistence();
          boolean fromJson = srcResultSet.isFromJson();

          Map<String, Class<? extends BaseDataType>> legacyToType = null;
          Map<String, Integer> legacyToIndex = null;
          if (fromJson)
          {
             legacyToType = new HashMap<>();
             legacyToIndex = new HashMap<>();

             List<LegacyFieldInfo> legacyFields = TableMapper.getAllLegacyFieldInfo(dstBuf.tempTableRef);
             for (LegacyFieldInfo lfi : legacyFields)
             {
                legacyToType.put(lfi.getLegacyName().toLowerCase(), lfi.getDataType());
             }

             for (int i = 0; i < dstProperties.length; i++)
             {
                String dstPropName = dstProperties[i];
                String legacyName = dstBuf.getDmoInfo().getFieldInfo(dstPropName).legacyLower;
                legacyToIndex.put(legacyName, i);
             }
          }

          // in next lines the TempRecord [dstRec] is processed at lower-level than usual in order to avoid the
          // extra cost of extra maintenance (locking, triggers, dirty checks) which is not expected to happen
          // as result of these record being inserted into the database. However, the index must be updated and
          // the [ffCache] must be in synch, so we will invalidate all its data:
          dstBuf.invalidateFFCache(0);

          ArrayList<PropertyDefinition> srcProperties = srcResultSet.getProperties();
          Iterator<Object[]> rowIter = srcResultSet.rowIterator();
          Iterator<Object[]> rowMetaIter = srcResultSet.rowMetaIterator();
          while (rowIter.hasNext())
          {
             Object[] row = rowIter.next();
             Object[] rowMeta = rowMetaIter.next();

             TempRecord dstRec = dstBuf.instantiateDMO();
             dstRec.initialize(dstBuf.getSession(), false);
             Long id = dstBuf.nextPrimaryKey();

             if (fromJson)
             {
                Object[] newRow = new Object[dstProperties.length];
                for (int i = 0; i < srcProperties.size(); i++)
                {
                   PropertyDefinition srcProp = srcProperties.get(i);
                   String legacyName = srcProp.getLegacyName().toLowerCase();

                   Object data = row[i];
                   Integer newIdx = legacyToIndex.get(legacyName);
                   if (newIdx == null)
                   {
                      LOG.log(Level.WARNING, "In createRecords(): unknown legacy name: " + legacyName);
                      continue;
                   }

                   if (data instanceof BaseDataType || (data != null && data.getClass().isArray()))
                   {
                      // SOAP requests build the fields as BDT already; the marker 'fromJson' remains.
                      newRow[newIdx] = data;
                   }
                   else
                   {
                      BaseDataType var = BaseDataType.generateUnknown(legacyToType.get(legacyName));
                      if (data != null)
                      {
                         if (srcProp.getType() == unknown.class)
                         {
                            // this is a marker for de-serializing explicitly for real JSON data

                            switch (var.getClass().getSimpleName())
                            {
                               case "date":
                                  var.assign(date.parseLiteral(data.toString()));
                                  break;
                               case "datetime":
                                  var.assign(datetime.parseLiteral(data.toString()));
                                  break;
                               case "datetimetz":
                                  var.assign(datetimetz.parseLiteral(data.toString()));
                                  break;

                               case "blob":
                               case "raw":
                               case "memptr":
                                  ((BinaryData) var).assign(Base64.safeBase64ToByteArray(data.toString()));
                                  break;

                               default:
                                  Stream.assignDatum(var, data.toString(), true);
                            }
                         }
                         else
                         {
                            Stream.assignDatum(var, data.toString(), true);
                         }
                      }

                      newRow[newIdx] = var;
                   }
                }

                row = newRow;
             }

             for (int i = 0; i < datums.length; i++)
             {
                DatumAccess da = datums[i];
                Method setter = da.getAccessor();
                Integer extent = da.getExtent();
                if (extent == null)
                {
                   Object data = row[i];
                   if (data instanceof unknown)
                   {
                      data = BaseDataType.generateUnknown(da.getPropertyMeta().getType());
                   }
                   Utils.invoke(setter, dstRec, data);
                }
                else
                {
                   // indexed setter invocation
                   BaseDataType[] data = (BaseDataType[]) row[i];
                   int size = extent;
                   for (int idx = 0; idx < size; idx++)
                   {
                      Object _data = data[idx];
                      if (_data instanceof unknown)
                      {
                         _data = BaseDataType.generateUnknown(da.getPropertyMeta().getType());
                      }
                      Utils.invoke(setter, dstRec, idx, _data);
                   }
                }
             }

             // set the rowState, errorString and errorFlag
             dstRec._rowState((Integer) rowMeta[TempRecord._ROW_STATE_DATA_INDEX]);
             dstRec._peerRowid((Long) rowMeta[TempRecord._PEER_ROWID_DATA_INDEX]); // this will be processed later on
             // TODO: originRowId - does it have any meaning?
             // TODO: datasourceRowID - does it have any meaning?
             dstRec._errorFlags((Integer) rowMeta[TempRecord._ERROR_FLAG_DATA_INDEX]);
             dstRec._errorString((String) rowMeta[TempRecord._ERROR_STRING_DATA_INDEX]);

             savedAny = true;

             if (dstNewIds != null)
             {
                dstRec.primaryKey(id);
                dstNewIds.put((Long) rowMeta[TempRecord._PK_DATA_INDEX], dstRec);
             }
             else
             {
                persistence.save(dstRec, id);
             }
          }

          return savedAny;
       }

       /**
        * Mark the specified temp-tables as having the REFERENCE-ONLY option.
        *
        * @param    dmos
        *           Variable length list of DMO instances returned by previous calls to {@link #define}.
        */
       public static void referenceOnly(Temporary... dmos)
       {
          for (Temporary dmo : dmos)
          {
             TemporaryBuffer buf = ((TemporaryBuffer) get(dmo));
             buf.referenceOnly = true;
             if (buf.master != null)
             {
                buf = buf.master;
             }

             Set<TemporaryBuffer> explicitBuffers = buf.getAllSlaveBuffers();
             if (explicitBuffers != null)
             {
                for (TemporaryBuffer buf2 : explicitBuffers)
                {
                   buf2.referenceOnly = true;
                }
             }
          }
       }

       /**
        * Register code page expression for the CLOB field. Code page will be evaluated and applied in
        * {@link #openScope()}.
        *
        * @param dmo
        *        DMO instance returned by previous calls to {@link #define}.
        * @param property
        *        Hibernate property name of the CLOB field.
        * @param codePageExpr
        *        Code page expression.
        */
       public static void registerCodePage(Temporary dmo, String property, Supplier codePageExpr)
       {
          ((TemporaryBuffer) get(dmo)).registerCodePage(property, codePageExpr);
       }

       /**
        * Open scope for the dynamic buffers which back the specified DMO instances at the given block
        * depth.
        *
        * @param blockDepth
        *        Zero-based depth of the block (starting from the outermost scope) at which the
        *        buffer scopes are opened.
        * @param dmos
        *        Variable length list of DMO instances returned by previous calls to {@link #define}.
        */
       public static void openScopeAt(int blockDepth, DataModelObject... dmos)
       {
          for (DataModelObject dmo : dmos)
          {
             get(dmo).openScopeAt(blockDepth);
          }
       }

       /**
        * Read all rows from the table targeted by the specified buffer and return each row as an
        * array, each row's values in the ordered specified by the given property list.
        *
        * @param    srcDMO
        *           The source DMO used to read the data.
        * @param    props
        *           The properties needed for this DMO.
        *
        * @return   The table rows, in a 2-element array; on index 0, the record; on index 1, the
        *           record's meta information.
        */
       protected static List<Object[][]> readAllRows(Temporary srcDMO, ArrayList<PropertyDefinition> props)
       {
          // get source buffer, which must be a TemporaryBuffer instance
          TemporaryBuffer srcBuf = (TemporaryBuffer) ((BufferReference) srcDMO).buffer();
          Map<String, Method> getters = srcBuf.getDmoInfo().getPojoGetterMap();
          Map<Method, PropertyMeta> metas = srcBuf.getDmoInfo().getPropsByGetterMap();

          List<Object[][]> data = new ArrayList<>();

          // row handler which stores an array of property values for each row found in the list to
          // be returned
          Consumer<TempRecord> worker = (dmo) ->
          {
             try
             {
                int idx = 0;
                Object[] row = new Object[props.size()];
                for (int j = 0; j < props.size(); j++)
                {
                   PropertyDefinition property = props.get(j);
                   Method getter = getters.get(property.getName());
                   PropertyMeta meta = metas.get(getter);
                   DataHandler handler = meta.getDataHandler();
                   int offset = meta.getOffset();

                   if (property.isExtent())
                   {
                      int extentSize = property.getExtent();
                      BaseDataType[] extentArray = new BaseDataType[extentSize];

                      for (int i = 0; i < extentSize; i++)
                      {
                         extentArray[i] = handler.getField(dmo, offset + i);
                      }

                      row[idx++] = extentArray;
                   }
                   else
                   {
                      Object bdt = handler.getField(dmo, offset);
                      if (bdt instanceof blob)
                      {
                         ((blob) bdt).readData();
                      }
                      else if (bdt instanceof clob)
                      {
                         ((clob) bdt).readData();
                      }

                      row[idx++] = bdt;
                   }
                }

                Object[] rowMeta = dmo.toArray();

                data.add(new Object[][] { row, rowMeta });
             }
             catch (SQLException exc)
             {
                throw new RuntimeException("Error reading DMO property", exc);
             }
          };

          srcBuf.readAllRows(worker);

          return data;
       }

       /**
        * Create a dynamic buffer for the temporary table. For creation of default buffers for dynamic
        * tables use {@link #createDefaultDynamicBufferForTempTable(TempTableBuilder)}.
        * <p>
        * Because the buffer is created dynamically it is bound to global scope, allowing it to survive the scope
        * where it was created. because of this, it must be manually finalized.
        *
        * @param  bufferName
        *         Target buffer name (4GL form).
        * @param  master
        *         Master buffer (default buffer of the target table).
        *
        * @return DMO proxy of the created buffer.
        */
       protected static Temporary createDynamicBufferForTempTable(String bufferName, TemporaryBuffer master)
       {
          if (master.mutableHandler != null && master.mutableHandler.bound != null)
          {
             // if the buffer is bound to another buffer, use that.
             master = (TemporaryBuffer) ((BufferImpl) master.mutableHandler.bound).buffer();
          }

          return createDynamicBufferForTempTable(bufferName, master, master.getParentTable());
       }

       /**
        * Create the default dynamic buffer for the dynamic temporary table.
        *
        * @param  tempTable
        *         The temp-table builder object associated with the target table.
        *
        * @return DMO proxy of the created buffer.
        */
       protected static Temporary createDefaultDynamicBufferForTempTable(TempTableBuilder tempTable)
       {
          return createDynamicBufferForTempTable(tempTable.name().getValue(), null, tempTable);
       }

       /**
        * Create a dynamic buffer for the temporary table.
        * <p>
        * Because the buffer is created dynamically it is bound to global scope, allowing it to survive the scope
        * where it was created. because of this, it must be manually finalized.
        *
        * @param  bufferName
        *         Target buffer name (4GL form).
        * @param  master
        *         Master buffer (default buffer of the target table). Can be {@code null} if the
        *         default buffer for the table is created.
        * @param  tempTable
        *         The temp-table object associated with the target table.
        *
        * @return DMO proxy of the created buffer.
        */
       private static Temporary createDynamicBufferForTempTable(String bufferName,
                                                                TemporaryBuffer master,
                                                                TempTable tempTable)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             return CREATE_BUFFER.timerWithReturn(
                () -> createDynamicBufferForTempTableImpl(bufferName, master, tempTable));
          }
          else
          {
             return createDynamicBufferForTempTableImpl(bufferName, master, tempTable);
          }
       }

       /**
        * Create a dynamic buffer for the temporary table.
        * <p>
        * Because the buffer is created dynamically it is bound to global scope, allowing it to survive the scope
        * where it was created. because of this, it must be manually finalized.
        *
        * @param  bufferName
        *         Target buffer name (4GL form).
        * @param  master
        *         Master buffer (default buffer of the target table). Can be {@code null} if the
        *         default buffer for the table is created.
        * @param  tempTable
        *         The temp-table object associated with the target table.
        *
        * @return DMO proxy of the created buffer.
        */
       private static Temporary createDynamicBufferForTempTableImpl(String bufferName,
                                                                    TemporaryBuffer master,
                                                                    TempTable tempTable)
       {
          Temporary temporary;
          final BufferManager bufferManager = master != null ? master.bufferManager : BufferManager.get();
          String uniqueBufferName = bufferManager.generateBufferName(bufferName);
          if (master != null)
          {
             temporary = TemporaryBuffer.define(null,
                                                (Temporary) master.getDMOProxy(),
                                                uniqueBufferName,
                                                bufferName,
                                                0,
                                                false,
                                                false);
          }
          else
          {
             temporary = TemporaryBuffer.define(null,
                                                tempTable.getDMOBufInterface(),
                                                uniqueBufferName,
                                                bufferName,
                                                true,
                                                tempTable,
                                                0,
                                                false);
          }
          TemporaryBuffer temporaryBuffer = ((TemporaryBuffer) RecordBuffer.get(temporary));
          temporaryBuffer.setDynamic();
          temporaryBuffer.setTempTableRef(tempTable);
          ((BufferImpl) temporary).interlink(); // HandleChain implementation
          TemporaryBuffer.openScopeAt(0 /*blockDepth*/, temporary);

          return temporary;
       }

       /**
        * Factory method to construct a new temporary buffer.
        *
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API.  This must be a
        *          subinterface of {@link Temporary}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   global
        *          <code>true</code> if the buffer survives until end of context
        *          life;  <code>false</code> if it expires upon leaving the top
        *          level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        *
        * @return  A new instance of this class.
        */
       private static <T extends TempTableBuffer>
       TemporaryBuffer makeBuffer(Class<?> def,
                                  Class<T> dmoBufIface,
                                  String variable,
                                  boolean global,
                                  UndoStateProvider undoable)
       {
          return makeBuffer(def, dmoBufIface, variable, global, undoable, -1);
       }

       /**
        * Factory method to construct a new temporary buffer.
        *
        * @param   def
        *          The currently executing OE class or external program.
        * @param   dmoBufIface
        *          Interface which defines DMO's public API. This must be a subinterface of
        *          {@link TempTableBuffer}.
        * @param   variable
        *          Name of the variable with which the record buffer will be
        *          associated in the transaction manager.
        * @param   global
        *          {@code true} if the buffer survives until end of context life;  {@code false} if it
        *          expires upon leaving the top level procedure scope.
        * @param   undoable
        *          An {@code UndoStateProvider} that manages the UNDO status of the table of this
        *          buffer.
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer is defined or {@code -1} for the current scope.
        *
        * @return  A new instance of this class.
        */
       private static <T extends TempTableBuffer>
       TemporaryBuffer makeBuffer(Class<?> def,
                                  Class<T> dmoBufIface,
                                  String variable,
                                  boolean global,
                                  UndoStateProvider undoable,
                                  int blockDepth)
       {
          if (!DatabaseManager.isTempTable(dmoBufIface))
          {
             // will never happen due [dmoBufIface] extends [TempTableBuffer]
             throw new IllegalArgumentException("DMO interface must extend Temporary");
          }

          return new TemporaryBuffer(def, dmoBufIface, variable, global, undoable, blockDepth);
       }

       /**
        * Copy all records from one temp table to another, optionally appending
        * to records already present in the destination table.
        * <p>
        * TODO:  it seems this method can be optimized to use a DMO's deepCopy()
        * method instead of reflection when the source and destination DMO types
        * are the same.
        *
        * @param   srcDMO
        *          DMO instance returned by a previous call to {@link #define} on
        *          the source temporary buffer.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on
        *          the destination temporary buffer.
        * @param   append
        *          <code>true</code> to add new records without affecting
        *          existing records;  <code>false</code> to remove all existing
        *          records before the copy takes place.
        * @param   errorMode
        *          Table copy mode, affects error handling.
        *
        * @return  <code>true</code> if copy was successful. <code>false</code> if validation error
        *          has occurred or the table is appended to itself.
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the
        *          underlying DMOs to get or set data values.
        */
       static boolean copyAllRows(Temporary srcDMO, Temporary dstDMO, boolean append, CopyTableMode errorMode)
       {
          return copyAllRows(null, srcDMO, dstDMO, append, errorMode, false, false, false);
       }

       /**
        * Copy all records from one temp table to another, optionally appending
        * to records already present in the destination table.
        * <p>
        * TODO:  it seems this method can be optimized to use a DMO's deepCopy()
        * method instead of reflection when the source and destination DMO types
        * are the same.
        *
        * @param   srcRec
        *          The {@link rowid} of the current record in the source buffer - if not null, it will
        *          be set in the destination buffer, too.
        * @param   srcDMO
        *          DMO instance returned by a previous call to {@link #define} on
        *          the source temporary buffer.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on
        *          the destination temporary buffer.
        * @param   append
        *          <code>true</code> to add new records without affecting
        *          existing records;  <code>false</code> to remove all existing
        *          records before the copy takes place.
        * @param   errorMode
        *          Table copy mode, affects error handling.
        *
        * @return  <code>true</code> if copy was successful. <code>false</code> if validation error
        *          has occurred or the table is appended to itself.
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the
        *          underlying DMOs to get or set data values.
        */
       static boolean copyAllRows(rowid     srcRec,
                                  Temporary srcDMO,
                                  Temporary dstDMO,
                                  boolean append,
                                  CopyTableMode errorMode)
       {
          return copyAllRows(srcRec, srcDMO, dstDMO, append, errorMode, false, false, false);
       }

       /**
        * Copy all records from one temp table to another, optionally appending to records already present in the
        * destination table.
        *
        * @param   srcRecId
        *          The {@link rowid} of the current record in the source buffer - if not null, it will be set in
        *          the destination buffer, too.
        * @param   srcDMO
        *          DMO instance returned by a previous call to {@link #define} on the source temporary buffer.
        * @param   dstDMO
        *          DMO instance returned by a previous call to {@link #define} on the destination temporary
        *          buffer.
        * @param   append
        *          {@code true} to add new records without affecting existing records;
        *          {@code false} to remove all existing records before the copy takes place.
        * @param   errorMode
        *          Table copy mode, affects error handling.
        * @param   skipUniqueConflicts
        *          Corresponds append-mode in COPY-TEMP-TABLE. If there is a unique index on the target temp-table
        *          and a row with a duplicate key is found, the row is not replaced. This parameter is ignored if
        *          {@code replaceMode} is {@code true}.
        * @param   replaceMode
        *          Corresponds replace-mode in COPY-TEMP-TABLE. When {@code true} records in the target temp-table
        *          are replaced with corresponding records from the source temp-table. The target temp-table must
        *          have a unique primary index that be can used to find the corresponding record. When the
        *          corresponding record is found in the target temp-table, the target record is replaced with the
        *          source record.
        * @param   looseCopy
        *          Corresponds loose-copy-mode in COPY-TEMP-TABLE. Only the fields with the same name that appear
        *          in both the source and target temp-table are copied. Other fields are ignored.
        *          <p><b>Note:</b><br>
        *          Because of an undocumented bug in OE, if the {@code replaceMode} is {@code true} the value will
        *          'leak' and overwrite this parameter. As result, a temp-table can be copied to another,
        *          incompatible temp-table even if {@code looseCopy} is explicitly set to {@code false}, just by
        *          passing {@code true} for the previous parameter.
        *
        * @return  {@code true} if copy was successful.
        *          {@code false} if validation error has occurred or the table is appended to itself.
        *
        * @throws  ErrorConditionException
        *          if a recoverable error occurs copying records.
        * @throws  RuntimeException
        *          if an unrecoverable error occurs invoking methods on the underlying DMOs to get or set data
        *          values.
        */
       static boolean copyAllRows(rowid srcRecId,
                                  Temporary srcDMO,
                                  Temporary dstDMO,
                                  boolean append,
                                  CopyTableMode errorMode,
                                  boolean skipUniqueConflicts,
                                  boolean replaceMode,
                                  boolean looseCopy)
       {
          if (replaceMode)
          {
             // see the javadoc note above for [looseCopy] parameter
             looseCopy = true;
          }

          // get source and destination buffers, both of which must be TemporaryBuffer instances
          TemporaryBuffer srcBuf = (TemporaryBuffer) ((BufferReference) srcDMO).buffer();
          TemporaryBuffer dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).buffer();

          // we need to check if this is the real bound buffer or not
          if (srcBuf.mutableHandler != null && srcBuf.mutableHandler.boundBuffer != null)
          {
             srcDMO = srcBuf.mutableHandler.bound;
             srcBuf = srcBuf.mutableHandler.boundBuffer;
          }
          if (dstBuf.mutableHandler != null && dstBuf.mutableHandler.boundBuffer != null)
          {
             dstDMO = dstBuf.mutableHandler.bound;
             dstBuf = dstBuf.mutableHandler.boundBuffer;
          }

          srcBuf.initialize();
          dstBuf.initialize();

          BufferImpl srcB4DMO = null;
          BufferImpl dstB4DMO = null;
          TemporaryBuffer srcB4Buf = null;
          TemporaryBuffer dstB4Buf = null;
          boolean before = false;

          BufferImpl dstBufImpl = (BufferImpl) dstDMO;
          BufferImpl srcBufImpl = (BufferImpl) srcDMO;
          if (srcBufImpl.isAfterBuffer() && dstBufImpl.isAfterBuffer())
          {
             srcB4DMO = (BufferImpl) srcBufImpl.beforeBufferNative();
             dstB4DMO = (BufferImpl) dstBufImpl.beforeBufferNative();
             srcB4Buf = (TemporaryBuffer) ((BufferReference) srcB4DMO).buffer();
             dstB4Buf = (TemporaryBuffer) ((BufferReference) dstB4DMO).buffer();

             srcB4Buf.initialize();
             dstB4Buf.initialize();
             before = true;
          }

          boolean inTx = false;
          boolean shouldRollback = false;

          try
          {
             inTx = dstBuf.persistenceContext.beginTransaction(null);
             srcBuf.flush();
             // Flushing will not guarantee that non-indexed fields are also flushed.
             // This check will make sure that the any such scenario with an available
             // key in the nursery is made visible before we copy the records.
             RecordNursery srcNursery = srcBuf.getPersistenceContext().getNursery();
             if (srcNursery.containsRecordBufferDmo(srcBuf))
             {
                srcNursery.makeVisible(srcBuf.getDmoInfo().getId(), srcBuf.getMultiplexID(), 0);
             }

             dstBuf.flush();

             if (before)
             {
                srcB4Buf.flush();
                dstB4Buf.flush();
             }

             if (inTx)
             {
                try
                {
                   dstBuf.persistenceContext.commit();
                   shouldRollback = false;
                }
                catch (PersistenceException exc)
                {
                   // TODO: what to do here?
                   if (LOG.isLoggable(Level.WARNING))
                   {
                      LOG.log(Level.WARNING, "Error flushing before buffers", exc);
                   }
                   shouldRollback = true;
                }
             }
          }
          catch (ValidationException | PersistenceException exc)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Failed flushing the after buffers or before buffers on copyAllRows", exc);
             }

             shouldRollback = true;
             ErrorManager.recordOrThrowError(exc);

          }
          finally
          {
              try
              {
                 // clear the transaction somehow
                 if (inTx && shouldRollback)
                 {
                    dstBuf.persistenceContext.rollback(true);
                 }
              }
              catch (PersistenceException exc)
              {
                 DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
                 ErrorManager.recordOrThrowError(exc);
              }
          }

          AbstractTempTable srcTempTable = srcBuf.getParentTable();
          AbstractTempTable dstTempTable = dstBuf.getParentTable();

          String srcBufName = ((HandleChain) srcDMO)._name();
          String dstBufName = ((HandleChain) dstDMO)._name();

          if (srcTempTable.equals(dstTempTable))
          {
             if (errorMode == CopyTableMode.COPY_TEMP_TABLE_MODE)
             {
                String msg = "Source and target are the same table in temp-table copy operation for " +
                             dstBufName;
                ErrorManager.displayError(12301, msg, false);

                return false;
             }

             if (append)
             {
                String msg = srcTempTable._name() + " cannot be appended to itself";
                ErrorManager.recordOrShowError(5362, msg, false, false);

                return false;
             }

             return true;
          }

          boolean simpleCopy = srcBuf.getDMOImplementationClass().equals(dstBuf.getDMOImplementationClass());

          // simpleCopy can be true even if the tables names are different, but the sqlSignatures match
          if(!simpleCopy)
          {
             String srcSignature = srcBuf.getDmoInfo().getSignature().getSqlSignature();
             String dstSignature = dstBuf.getDmoInfo().getSignature().getSqlSignature();
             simpleCopy = srcSignature.equals(dstSignature);
          }

          // PROGRESS does not care if the field names are different; what it cares about are only the field type
          // and position - if a source field has the same position and type as the destination field, then they
          // are a match. If the source and destination fields at a certain position do not match, then PROGRESS
          // will throw an error and abend the application.

          // if the backing tables are different, check if the fields match
          Map<DatumAccess, DatumAccess> propsMap = null;
          Map<DatumAccess, DatumAccess> propsB4Map = null;

          if (!simpleCopy)
          {
             // we need to modify the setter properties, so instantiate a new map
             propsMap = createPropsMap(looseCopy, srcBufImpl, dstBufImpl, errorMode);
             if (propsMap == null)
             {
                return false;
             }

             if (before)
             {
                propsB4Map = createPropsMap(looseCopy, srcB4DMO, dstB4DMO, errorMode);

                if (propsB4Map == null)
                {
                   return false;
                }
             }
          }
          else
          {
             looseCopy = false;
          }

          Persistence persistence = srcBuf.getPersistence();
          String srcBufRenamed = null;
          boolean savedAny = false;
          int commit = 0;   // 0 = do nothing, 1 = commit, -1 = rollback
          boolean validationError = false;
          boolean copyError = false;
          rowid destRecId = null;

          try
          {
             commit = (persistence.beginTransaction(Persistence.TEMP_CTX) ? 1 : 0);
             boolean dstQueried = dstBuf.isQueried();
             boolean dstB4Queried = before && dstB4Buf.isQueried();

             // if not in append mode, first remove records (if any) from destination table.
             if (!append)
             {
                if (!dstBuf.hasOoDestructors() && !dstQueried)
                {
                   dstBuf.removeRecords(null, null, null);
                }
                else
                {
                   dstBuf.loopDelete(null, null, null);
                }
                if (before)
                {
                   if (!dstB4Buf.hasOoDestructors() && !dstB4Queried)
                   {
                      dstB4Buf.removeRecords(null, null, null);
                   }
                   else
                   {
                      dstB4Buf.loopDelete(null, null, null);
                   }
                }
             }

             if (!srcBuf.isTableDefinitelyEmpty())
             {
                // if the destination is empty, replace works as append
                boolean removeAppend = ((replaceMode || append) && dstBuf.isTableDefinitelyEmpty());
                boolean removeAppendB4 = ((replaceMode || append) && (dstB4Buf == null || dstB4Buf.isTableDefinitelyEmpty()));
                if (!dstQueried && ((removeAppend && removeAppendB4) || !replaceMode))
                {
                   Context ctx = srcBuf.local;
                   boolean doBeforeCopy = srcB4Buf != null && dstB4Buf != null && !srcB4Buf.isTableDefinitelyEmpty();

                   FastCopyContext masterCopyContext = new FastCopyContext();
                   masterCopyContext.setSimpleCopy(simpleCopy)
                                    .setSourceBuffer(srcBuf)
                                    .setDestinationBuffer(dstBuf)
                                    .setPropsMap(propsMap)
                                    .setAppendMode(removeAppend ? false : append)
                                    .setLooseCopyMode(looseCopy)
                                    .setForceMapping(doBeforeCopy || srcRecId != null)
                                    .setIsBefore(false);
                   FastCopyHelper masterCopyHelper = ctx.getFastCopyHelper(masterCopyContext);

                   FastCopyHelper beforeCopyHelper = null;
                   FastCopyContext beforeCopyContext = null;
                   if (doBeforeCopy)
                   {
                      beforeCopyContext = new FastCopyContext();
                      beforeCopyContext.setSimpleCopy(simpleCopy)
                                       .setSourceBuffer(srcB4Buf)
                                       .setDestinationBuffer(dstB4Buf)
                                       .setPropsMap(propsB4Map)
                                       .setAppendMode(removeAppendB4 ? false : append)
                                       .setLooseCopyMode(looseCopy)
                                       .setForceMapping(true)
                                       .setIsBefore(true);
                      beforeCopyHelper = ctx.getFastCopyHelper(beforeCopyContext);
                   }

                   if (masterCopyHelper.canUseFastCopy() &&
                      (beforeCopyHelper == null || beforeCopyHelper.canUseFastCopy()))
                   {
                      destRecId = masterCopyHelper.executeCopy(masterCopyContext, srcRecId);
                      if (doBeforeCopy)
                      {
                         beforeCopyHelper.executeCopy(beforeCopyContext, null);

                         // we need to remap the copied records from the master table with the ones in the
                         // before table
                         FastCopyHelper.updatePeerRecords(masterCopyHelper,
                                                          masterCopyContext,
                                                          beforeCopyHelper,
                                                          beforeCopyContext);
                         beforeCopyHelper.clear(beforeCopyContext);
                      }

                      masterCopyHelper.clear(masterCopyContext);
                      savedAny = true;
                      dstBuf.invalidateFFCache(0);
                      if (before)
                      {
                         dstB4Buf.invalidateFFCache(0);
                      }

                      return true;
                   }
                }

                String replaceQuery = null;

                if (replaceMode)
                {
                   P2JIndex primaryIndex = dstBuf.getDmoInfo().getPrimaryIndex(true);
                   if (primaryIndex == null || !primaryIndex.isUnique())
                   {
                      ErrorManager.recordOrShowError(11885);
                      // A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table.
                      return false;
                   }

                   DmoMeta dstDMOInfo = dstBuf.getDmoInfo();
                   DmoMeta srcDMOInfo = srcBuf.getDmoInfo();

                   for (P2JIndexComponent comp : primaryIndex.components())
                   {
                      // Get destination property
                      Property dstProp = dstDMOInfo.byLegacyName(comp.getLegacyName());

                      // Get source property
                      Property srcProp = srcDMOInfo.byLegacyName(comp.getLegacyName());
                      if (srcProp == null)
                      {
                         ErrorManager.recordOrShowError(11885);
                         return false;
                      }
                      // Compare types
                      if (!((dstProp._fwdType == srcProp._fwdType)                                 ||
                            (dstProp._fwdType == integer.class && srcProp._fwdType == int64.class) ||
                            (dstProp._fwdType == int64.class && srcProp._fwdType == integer.class)))
                      {
                         // Types do not match
                         ErrorManager.recordOrShowError(11885);
                         return false;
                      }
                   }

                   if (dstBuf.getLegacyName().equals(srcBuf.getLegacyName()))
                   {
                      BufferManager bm = BufferManager.get();
                      // we have a name collision
                      srcBufRenamed = srcBuf.getLegacyName() + "_db_" + srcBuf.buffer().getDmoInfo().sqlTable;
                      srcBuf.overrideLegacyName(srcBufRenamed);
                      srcBuf.overrideDMOAlias(bm.generateBufferName(srcBufRenamed));
                   }

                   StringBuilder sb = new StringBuilder("WHERE ");
                   ArrayList<P2JIndexComponent> comps = primaryIndex.components();
                   for (int i = 0; i < comps.size(); i++)
                   {
                      P2JIndexComponent comp = comps.get(i);
                      if (i > 0)
                      {
                         sb.append(" and ");
                      }

                      sb.append(dstBuf.getLegacyName()).append(".").append(comp.getLegacyName()).append("=")
                        .append(srcBufRenamed == null ? srcBuf.getLegacyName() : srcBufRenamed)
                                              .append(".").append(comp.getLegacyName());
                   }

                   replaceQuery = sb.toString();
                }

                // get the list of source records to be copied.  These will be all records in
                // srcBuf's backing temp table which have srcDMO's multiplex ID.
                FQLExpression fqlBuilder = new FQLExpression("from ");
                fqlBuilder.append(srcBuf.getDMOImplementationName());

                // srcBuf is a TemporaryBuffer, so it isMultiplexed()
                fqlBuilder.append(" where ");
                fqlBuilder.append(MULTIPLEX_FIELD_NAME);
                fqlBuilder.append(" = ", true);

                // force the records to be ordered by their ID
                // TODO: in copy-temp-table sorting order affects which record remains in constraints
                //       violation case. The correct sorting is by primary index if it presents or by
                //       the index with the lexicographically first name.
                fqlBuilder.append(" order by ");
                fqlBuilder.append(MULTIPLEX_FIELD_NAME);
                fqlBuilder.append(",");
                fqlBuilder.append(Session.PK);

                String fql = fqlBuilder.toFinalExpression();
                Object[] parms = new Object[] { srcBuf.getMultiplexID() };
                ScrollableResults<TempRecord> results =
                      persistence.scroll(null, fql, parms, 0, 0, ResultSet.TYPE_FORWARD_ONLY);

                BufferManager bufMgr = srcBuf.getBufferManager();

                try
                {
                   while (results.next())
                   {
                      // copy each source record into a new destination DMO instance
                      Object[] row = results.get();
                      TempRecord srcRec = (TempRecord) row[0];
                      srcBuf.loadRecord(srcRec);
                      TempRecord dstRec = null;
                      boolean createNew = true;

                      if (replaceMode)
                      {
                         logical found = new logical();
                         String finalReplaceQuery = replaceQuery;
                         ErrorManager.nestedSilent(() -> found.assign(dstBufImpl._find(finalReplaceQuery,
                                                                                       BufferImpl.FindMode.UNIQUE,
                                                                                       LockType.EXCLUSIVE,
                                                                                       srcBufImpl)));
                         if (found.booleanValue())
                         {
                            // do not create a new record. An existing record matching the index was found and is
                            // loaded into buffer
                            createNew = false;
                            dstRec = ((TempRecord) dstBufImpl.buffer().getCurrentRecord());
                         }
                      }

                      if (createNew)
                      {
                         dstRec = dstBuf.instantiateDMO();
                         dstRec.initialize(dstBuf.getSession(), true);
                      }

                      if (simpleCopy)
                      {
                         Long dstId = dstRec.primaryKey();
                         dstRec.copy(srcRec);
                         dstRec._multiplex(dstBuf.multiplexID);
                         dstRec.primaryKey(dstId);
                      }
                      else
                      {
                         logical response = copy((DataModelObject) srcRec,
                                                 (DataModelObject) dstRec,
                                                 propsMap,
                                                 null);
                         if (!response.booleanValue())
                         {
                            copyError = true;
                            continue;
                         }
                      }

                      bufMgr.evictDMOIfUnused(persistence, srcBuf.getPersistenceContext(), srcRec);
                      if (LOG.isLoggable(Level.FINEST))
                      {
                         String msg = "Copy temp table record from MPID " +
                                      srcBuf.getMultiplexID() + " to MPID " +
                                      dstBuf.getMultiplexID() + "...";
                         LOG.log(Level.FINEST, msg);
                         LOG.log(Level.FINEST, srcBuf.describe() + srcBuf.toString(srcRec));
                         LOG.log(Level.FINEST, dstBuf.describe() + dstBuf.toString(dstRec));
                      }

                      Long id;
                      if (createNew)
                      {
                         id = dstBuf.nextPrimaryKey();
                         dstRec.primaryKey(id);
                      }
                      else
                      {
                         id = dstRec.primaryKey();
                      }

                      // validate a record against the target table
                      try
                      {
                         dstBuf.validate(dstRec);
                      }
                      catch (ValidationException e)
                      {
                         try
                         {
                            if (createNew)
                            {
                               // this is important to dealloc the reserved PK
                               DirectAccessHelper.clearPrimaryKey(dstBuf.getSession(), dstRec);
                            }
                         }
                         catch (DirectAccessException dae)
                         {
                            LOG.log(Level.SEVERE,
                                   "Failed deleting the transient record on validation exception", dae);
                         }
                         if (skipUniqueConflicts)
                         {
                            continue;
                         }
                         else
                         {
                            ErrorManager.recordOrShowError(e.getNumber(), e.getMessage(), false);
                            validationError = true;
                            break;
                         }
                      }

                      if (srcRecId != null && srcRecId.equals(new rowid(srcRec.primaryKey())))
                      {
                         destRecId = new rowid(id);
                      }

                      if (before && srcRec._rowState() != null && srcRec._rowState() > 0)
                      {
                         if (srcB4DMO.findByRowID(new rowid(srcRec._peerRowid())).getValue())
                         {
                            TempRecord srcB4Rec = ((TemporaryBuffer) srcB4DMO.buffer()).getCurrentRecord();
                            TempRecord dstB4Rec = dstB4Buf.instantiateDMO();
                            dstB4Rec.initialize(dstB4Buf.getSession(), false);

                            if (simpleCopy)
                            {
                               dstB4Rec.copy(srcB4Rec);
                               dstB4Rec._multiplex(dstB4Buf.multiplexID);
                            }
                            else
                            {
                               copy((DataModelObject) srcB4Rec, (DataModelObject) dstB4Rec, propsB4Map, null);
                            }
                            Long b4id = dstB4Buf.nextPrimaryKey();
                            dstB4Rec.primaryKey(b4id);

                            // now interlink:
                            dstRec._rowState(srcRec._rowState());
                            dstB4Rec._rowState(srcRec._rowState()); // same
                            dstRec._peerRowid(b4id);
                            dstB4Rec._peerRowid(id);

                            persistence.save(dstB4Rec, b4id);
                         }
                      }

                      persistence.save(dstRec, id, true);
                      savedAny = true;

                      // In replace mode, after the element is inserted, the cache needs to be invalidated
                      if(replaceMode)
                      {
                         dstBuf.invalidateFFCache(0);
                      }
                   }
                }
                catch (Throwable t) // debug block
                {
                   LOG.log(Level.FINER, "", t);
                   throw t;
                }
                finally
                {
                   if (results != null)
                   {
                      results.close();
                   }

                   // invalidate ff cache (see previous occurrence / instantiateDMO() for details)
                   dstBuf.invalidateFFCache(0);
                   if (before)
                   {
                      dstB4Buf.invalidateFFCache(0);
                   }
                }
             }

             if (before && !srcB4Buf.isTableDefinitelyEmpty())
             {
                // copy the DELETED records from srcB4Buf
                FQLExpression buf = new FQLExpression("from ");
                buf.append(srcB4Buf.getDMOImplementationName());
                buf.append(" where ");
                buf.append(MULTIPLEX_FIELD_NAME);
                buf.append(" = ", true);
                buf.append(" and _rowState = ");
                buf.append(String.valueOf(Buffer.ROW_DELETED));
                buf.append(" order by ");
                buf.append(Session.PK);

                String fql = buf.toFinalExpression();
                Object[] parms = new Object[] { srcB4Buf.getMultiplexID() };
                ScrollableResults<Record> results =
                      persistence.scroll(null, fql, parms, 0, 0, ResultSet.TYPE_FORWARD_ONLY);

                BufferManager bufMgr = srcB4Buf.getBufferManager();

                try
                {
                   while (results.next())
                   {
                      // copy each source record into a new destination DMO instance.
                      TempRecord srcB4Rec = (TempRecord) results.get()[0];
                      TempRecord dstB4Rec = dstB4Buf.instantiateDMO();
                      dstB4Rec.initialize(dstB4Buf.getSession(), false);

                      if (simpleCopy)
                      {
                         dstB4Rec.copy(srcB4Rec);
                         dstB4Rec._multiplex(dstB4Buf.multiplexID);
                      }
                      else
                      {
                         copy((DataModelObject) srcB4Rec, (DataModelObject) dstB4Rec, propsB4Map, null);
                      }

                      bufMgr.evictDMOIfUnused(persistence, srcB4Buf.getPersistenceContext(), srcB4Rec);
                      if (LOG.isLoggable(Level.FINEST))
                      {
                         String msg = "Copy temp table record from MPID " +
                                      srcB4Buf.getMultiplexID() + " to MPID " +
                                      dstB4Buf.getMultiplexID() + "...";
                         LOG.log(Level.FINEST, msg);
                         LOG.log(Level.FINEST, srcB4Buf.describe() + srcB4Buf.toString(srcB4Rec));
                         LOG.log(Level.FINEST, dstB4Buf.describe() + dstB4Buf.toString(dstB4Rec));
                      }

                      Long id = dstB4Buf.nextPrimaryKey();
                      dstB4Rec.primaryKey(id);
                      dstB4Rec._rowState(Buffer.ROW_DELETED); // this is what we searched for
                      // dstB4Rec._peerRowid(id); TODO: why was this set? There is no peer ID here

                      persistence.save(dstB4Rec, id);
                      savedAny = true;
                   }
                }
                finally
                {
                   if (results != null)
                   {
                      results.close();
                   }

                   // in case of only DELETE records the other invalidations will not be reached
                   dstB4Buf.invalidateFFCache(0);
                }
             }
          }
          catch (PersistenceException exc)
          {
             commit *= -1;
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             ErrorManager.recordOrThrowError(exc);
          }
          catch (IllegalAccessException | InstantiationException exc)
          {
             commit *= -1;

             throw new RuntimeException("Error creating record", exc);
          }
          catch (InvocationTargetException exc)
          {
             commit *= -1;
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);

             throw new RuntimeException("Error creating record", exc);
          }
          catch (StopConditionException exc)
          {
             // thrown by Persistence when a JDBC-level error was encountered; Hibernate session will
             // have been closed, don't try to commit or rollback below
             commit = 0;

             if (LOG.isLoggable(Level.SEVERE))
             {
                LOG.log(Level.SEVERE, "Error copying all temp table records", exc);
             }

             throw exc;
          }
          finally
          {
             if (savedAny)
             {
                dstBuf.local.nonEmptyTableFlags.add(dstBuf.multiplexID);
                if (before)
                {
                   dstB4Buf.local.nonEmptyTableFlags.add(dstB4Buf.multiplexID);
                }
             }

             try
             {
                switch (commit)
                {
                   case 1:
                      persistence.commit(Persistence.TEMP_CTX);
                      break;
                   case -1:
                      persistence.rollback(Persistence.TEMP_CTX);
                      break;
                   case 0:
                      break;
                }
             }
             catch (PersistenceException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
                ErrorManager.recordOrThrowError(exc);
             }

             if (srcBufRenamed != null)
             {
                srcBuf.restoreLegacyName();
                srcBuf.restoreDMOAlias();
             }

             if (destRecId != null)
             {
                ((Buffer) dstDMO).findByRowID(destRecId);
             }
          }
          if (validationError || copyError)
          {
             //Error found during COPY-TEMP-TABLE operation
             ErrorManager.recordOrShowError(12304,
                                            ((BufferImpl) srcDMO).doGetName(),
                                            ((BufferImpl) dstDMO).doGetName());
          }

          return !(validationError || copyError);
       }

       /**
        * Create a {@code DatumAccess} mapping for the source and destination DMOs.
        *
        * @param    looseCopy
        *           Corresponds loose-copy-mode in COPY-TEMP-TABLE. Only the fields with the same name
        *           that appear in both the source and target temp-table are copied. Other fields are
        *           ignored.
        * @param    srcDMO
        *           The source buffer.
        * @param    dstDMO
        *           The destination buffer.
        * @param    errorMode
        *           Table copy mode, affects error handling.
        *
        * @return   See above.
        */
       private static Map<DatumAccess, DatumAccess> createPropsMap(boolean looseCopy,
                                                                   BufferImpl srcDMO,
                                                                   BufferImpl dstDMO,
                                                                   CopyTableMode errorMode)
       {
          TemporaryBuffer srcBuf = (TemporaryBuffer) srcDMO.buffer();
          TemporaryBuffer dstBuf = (TemporaryBuffer) dstDMO.buffer();

          TempTable srcTempTable = srcBuf.getParentTable();
          TempTable dstTempTable = dstBuf.getParentTable();

          String srcBufName = srcDMO._name();
          String dstBufName = dstDMO._name();

          String[] srcProperties = TempTableBuilder.getOrderedPropertyNames(srcTempTable);
          String[] dstProperties = TempTableBuilder.getOrderedPropertyNames(dstTempTable);
          Map<String, Integer> srcExtents = srcBuf.getDmoInfo().getExtentMap();
          Map<String, Integer> dstExtents = dstBuf.getDmoInfo().getExtentMap();

          if (!looseCopy && (srcProperties.length != dstProperties.length))
          {
             handleTablesDoNotMatch(errorMode, srcBufName, dstBufName);
             return null;
          }

          String[] srcPropertiesLegacy = null;
          Map<String, Integer> dstPropertiesLegacy = null;

          if (looseCopy)
          {
             srcPropertiesLegacy = new String[srcProperties.length];
             for (int i = 0; i < srcProperties.length; i++)
             {
                srcPropertiesLegacy[i] =
                   srcBuf.getDmoInfo().getFieldInfo(srcProperties[i]).legacyLower;
             }

             dstPropertiesLegacy = new CaseInsensitiveHashMap<>();
             for (int i = 0; i < dstProperties.length; i++)
             {
                Property fieldInfo = dstBuf.getDmoInfo().getFieldInfo(dstProperties[i]);
                dstPropertiesLegacy.put(fieldInfo.legacy, i);
             }
          }

          Map<String, Method> srcGetters = srcBuf.getDmoInfo().getPojoGetterMap();
          Map<String, Method> dstGetters = dstBuf.getDmoInfo().getPojoGetterMap();
          Map<String, Method> dstSetters = dstBuf.getDmoInfo().getPojoSetterMap();

          Map<DatumAccess, DatumAccess> propsMap = new HashMap<>(); // TODO: reserved properties
          for (int i = 0; i < srcProperties.length; i++)
          {
             String dstPropName;
             if (looseCopy)
             {
                Integer idx = dstPropertiesLegacy.get(srcPropertiesLegacy[i]);
                if (idx == null)
                {
                   continue;
                }
                dstPropName = dstProperties[idx];
             }
             else if (i < dstProperties.length)
             {
                dstPropName = dstProperties[i];
             }
             else
             {
                handleTablesDoNotMatch(errorMode, srcBufName, dstBufName);
                return null;
             }

             String srcPropName = srcProperties[i];
             DatumAccess srcDatum = srcBuf.getGetterDatums().get(srcPropName);
             DatumAccess dstDatum = dstBuf.getSetterDatums().get(dstPropName);

             // compare data types
             Method srcGetter = srcDatum == null ? srcGetters.get(srcPropName) : srcDatum.getAccessor();
             Method dstGetter = dstGetters.get(dstPropName);
             Method dstSetter = dstDatum == null ? dstSetters.get(dstPropName) : dstDatum.getAccessor();
             Integer srcExtent = srcDatum == null ? srcExtents.get(srcPropName) : srcDatum.getExtent();
             Integer dstExtent = dstDatum == null ? dstExtents.get(dstPropName) : dstDatum.getExtent();
             ParmType srcType = ParmType.fromClass(srcGetter.getReturnType());
             ParmType dstType = ParmType.fromClass(dstGetter.getReturnType());

             boolean typesCompatible = (srcType == dstType) ||
                                       (srcType == ParmType.INT && dstType == ParmType.INT64) ||
                                       (srcType == ParmType.INT64 && dstType == ParmType.INT);

             if (srcType == null || dstType == null || !typesCompatible)
             {
                if (looseCopy)
                {
                   continue;
                }
                else
                {
                   handleTablesDoNotMatch(errorMode, srcBufName, dstBufName);
                   return null;
                }
             }

             // compare extent sizes
             long srcExtentSize = srcExtent == null ? 1 : srcExtent;
             long dstExtentSize = dstExtent == null ? 1 : dstExtent;

             if (srcExtentSize != dstExtentSize)
             {
                if (looseCopy)
                {
                   continue;
                }
                else
                {
                   handleTablesDoNotMatch(errorMode, srcBufName, dstBufName);
                   return null;
                }
             }

             if (srcDatum == null)
             {
                PropertyMeta srcPropMeta = srcBuf.getDmoInfo().getPropsByGetterMap().get(srcGetter);

                srcDatum = new DatumAccess(srcPropName,
                                           null,
                                           srcGetter,
                                           srcExtent,
                                           srcPropMeta,
                                           srcBuf.isPropertyIndexed(srcPropMeta.getOffset()),
                                           false);
                srcBuf.getGetterDatums().put(srcPropName, srcDatum);
             }

             if (dstDatum == null)
             {
                PropertyMeta dstPropMeta = dstBuf.getDmoInfo().getPropsBySetterMap().get(dstSetter);

                // we are on a setter dst method, with a non-extent property which has an ASSIGN trigger
                boolean dstAssignTrigger =
                         dstSetter.getReturnType() != Void.class &&
                         dstPropMeta.getOriginalExtent() == 0    &&
                         dstBuf.getTriggerTracker() != null      &&
                         dstBuf.getTriggerTracker().isTriggerEnabled(DatabaseEventType.ASSIGN,
                                                                     dstPropName,
                                                                     dstBuf.getLogicalDatabase());
                dstDatum = new DatumAccess(dstPropName,
                                           null,
                                           dstSetter,
                                           dstExtent,
                                           dstPropMeta,
                                           dstBuf.isPropertyIndexed(dstPropMeta.getOffset()),
                                           dstAssignTrigger);
                dstBuf.getSetterDatums().put(dstPropName, dstDatum);
             }

             propsMap.put(srcDatum, dstDatum);
          }

          return propsMap;
       }

       /**
        * This is the actual implementation of TEMP-TABLE buffer's GET-CHANGES method. No validation
        * is performed, the method expects the parameters to be validated in calling method.
        * <p>
        * What this method actually does is copy all records from source buffers to destination,
        * skipping the UNMODIFIED records from AFTER source, into destination. During the process,
        * the BEFORE/AFTER ROWIDs relation between the new records is maintained.
        *
        * @param   srcBefore
        *          The buffer for BEFORE buffer source. All records from this table are copied.
        * @param   srcAfter
        *          The buffer for AFTER buffer source. The non-modified records of this table are
        *          dropped.
        * @param   dstBefore
        *          The destination buffer for BEFORE records. With the exception of DELETEd records
        *          from this table will have a link (AFTER-ROWID) to a record in {@code dstAfter}.
        * @param   dstAfter
        *          The destination buffer for AFTER records. All records from this table will have
        *          a link (BEFORE-ROWID) to a record in {@code dstBefore}.
        */
       static boolean copyChanges(TemporaryBuffer srcBefore,
                                  TemporaryBuffer srcAfter,
                                  TemporaryBuffer dstBefore,
                                  TemporaryBuffer dstAfter)
       {
          // flush the records
          if (!srcAfter.getParentTable().flushBuffers())
          {
             return false;
          }

          if (srcBefore.isTableDefinitelyEmpty())
          {
             return true;
          }

          Persistence persistence = srcBefore.getPersistence();
          boolean savedAny = false;
          int commit = 0;   // 0 = do nothing, 1 = commit, -1 = rollback

          try
          {
             commit = persistence.beginTransaction(Persistence.TEMP_CTX) ? 1 : 0;

             // get the list of source records to be copied.  These will be all records in
             // srcBefore's backing temp table which have srcDMO's multiplex ID.
             FQLExpression beforeFql = new FQLExpression("from ");
             beforeFql.append(srcBefore.getDMOImplementationName());

             // srcBefore is a TemporaryBuffer, so it isMultiplexed()
             beforeFql.append(" where ").append(MULTIPLEX_FIELD_NAME).append(" = ", true);

             // force the records to be ordered by their ID
             // TODO in copy-temp-table sorting order affects which record remains in constraints
             // violation case. The correct sorting is by primary index if it presents or by
             // the index with the lexicographically first name.
             beforeFql.append(" order by ").append(Buffer.ROW_STATE_FIELD).append(",").append(Session.PK);

             ScrollableResults<Record> beforeRes =
                persistence.scroll(null,
                                   beforeFql.toFinalExpression(),
                                   new Object[] { srcBefore.getMultiplexID() },
                                   0,
                                   0,
                                   ResultSet.TYPE_FORWARD_ONLY);

             try
             {
                while (beforeRes.next())
                {
                   // Copy each source record into a new destination DMO instance.
                   TempRecord srcBeforeRec = (TempRecord) beforeRes.get()[0];
                   TempRecord dstBeforeRec = dstBefore.instantiateDMO();
                   dstBeforeRec.initialize(dstBefore.getSession(), false);

                   BaseRecord.copy(dstBeforeRec, srcBeforeRec, true);
                   Integer rowState = srcBeforeRec._rowState();
                   dstBeforeRec._rowState(rowState);

                   Long idChBefore = dstBefore.nextPrimaryKey();
                   dstBeforeRec.primaryKey(idChBefore);

                   // usually we need to duplicate the after-buffer destination, too. The only
                   // exception is, in fact, the DELETEd records which do not have a pair rowid record
                   if (srcBeforeRec._peerRowid() != null)
                   {
                      Record srcAfterRec = persistence.quickLoad(
                         new RecordIdentifier<>(srcAfter.getEntityName(), srcBeforeRec._peerRowid()),
                         Persistence.TEMP_CTX);
                      if (srcAfterRec != null)
                      {
                         TempRecord dstAfterRec = dstAfter.instantiateDMO();
                         dstAfterRec.initialize(dstAfter.getSession(), false);
                         BaseRecord.copy(dstAfterRec, srcAfterRec, true);
                         dstAfterRec._rowState(rowState);

                         Long idChAfter = dstAfter.nextPrimaryKey();
                         dstAfterRec.primaryKey(idChAfter);

                         // inter-link the newly created records:
                         dstAfterRec._peerRowid(idChBefore);
                         dstBeforeRec._peerRowid(idChAfter);
                         dstAfterRec._originRowid(srcBeforeRec.primaryKey());
                         dstBeforeRec._originRowid(srcBeforeRec.primaryKey());

                         persistence.save(dstAfterRec, idChAfter);
                      }
                      else
                      {
                         LOG.warning("Copy error: AFTER-RECORD not found for (" + srcBefore.getTable() +
                               ":" + Long.toHexString(srcBeforeRec._peerRowid()) + ")");
                      }
                   }
                   else
                   {
                      // normally these are DELETEd changes
                      dstBeforeRec._originRowid(srcBeforeRec.primaryKey());
                   }

                   persistence.save(dstBeforeRec, idChBefore);
                   savedAny = true;
                }
             }
             catch (ReflectiveOperationException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
                commit *= -1;
                ErrorManager.recordOrShowError(11924, srcAfter.getLegacyName(), dstAfter.getLegacyName());
                // MERGE-CHANGES run on original table <name> and change table <name> whose columns do not match exactly.
                return false;
             }
             finally
             {
                if (beforeRes != null)
                {
                   beforeRes.close();
                }
                // invalidate ff cache (see previous occurrence / instantiateDMO() for details)
                dstAfter.invalidateFFCache(0);
                dstBefore.invalidateFFCache(0);
             }
          }
          catch (PersistenceException exc)
          {
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             commit *= -1;
             ErrorManager.recordOrShowError(11924, srcAfter.getLegacyName(), dstAfter.getLegacyName());
             // MERGE-CHANGES run on original table <name> and change table <name> whose columns do not match exactly.
             return false;
          }
          finally
          {
             if (savedAny)
             {
                dstAfter.local.nonEmptyTableFlags.add(dstAfter.multiplexID);
                dstBefore.local.nonEmptyTableFlags.add(dstBefore.multiplexID);
             }

             try
             {
                switch (commit)
                {
                   case 1:
                      persistence.commit(Persistence.TEMP_CTX);
                      break;
                   case -1:
                      persistence.rollback(Persistence.TEMP_CTX);
                      break;
                   case 0:
                      break;
                }
             }
             catch (PersistenceException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
                ErrorManager.recordOrShowError(11924, srcAfter.getLegacyName(), dstAfter.getLegacyName());
                // MERGE-CHANGES run on original table <name> and change table <name> whose columns do not match exactly.
                return false;
             }
          }

          return true;
       }

       /**
        * Rejects changes to the data in the AFTER-BUFFER of this BEFORE-BUFFER.
        *
        * @param   after
        *          The AFTER-BUFFER to be processed.
        *
        * @return  {@code true} if operation is successful.
        */
       protected boolean rejectChanges(BufferImpl after)
       {
          if (isTableDefinitelyEmpty())
          {
             return true; // nothing to process
          }

          Persistence persistence = getPersistence();
          TemporaryBuffer afterDMO = (TemporaryBuffer) after.buffer();
          boolean savedAny = false;
          int commit = 0;   // 0 = do nothing, 1 = commit, -1 = rollback

          try
          {
             commit = persistence.beginTransaction(Persistence.TEMP_CTX) ? 1 : 0;

             FQLExpression fql = new FQLExpression("from ");
             fql.append(getDMOImplementationName());
             fql.append(" where ").append(TemporaryBuffer.MULTIPLEX_FIELD_NAME).append(" = ", true);
             fql.append(" order by ");
             fql.append(Session.PK);

             ScrollableResults<TempRecord> beforeRes =
                persistence.scroll(null,
                                   fql.toFinalExpression(),
                                   new Object[] { getMultiplexID() },
                                   0,
                                   0,
                                   ResultSet.TYPE_FORWARD_ONLY);

             try
             {
                while (beforeRes.next())
                {
                   // Copy each source record into a new destination DMO instance.
                   TempRecord srcBeforeRec = (TempRecord) beforeRes.get()[0];
                   Long afterRowid = srcBeforeRec._peerRowid();
                   afterDMO.setIgnoreBeforeTracking(true);
                   switch (srcBeforeRec._rowState())
                   {
                      case Buffer.ROW_DELETED:
                         // restore back the deleted record:
                         after.create();
                         Record deletedRec = afterDMO.getCurrentRecord();
                         BaseRecord.copy(deletedRec, srcBeforeRec, false);
                         Long idAfter = afterDMO.nextPrimaryKey();
                         afterDMO.rowState(Buffer.ROW_UNMODIFIED);
                         afterDMO.originRowid(new rowid());
                         after.release();
                         savedAny = true;
                         break;

                      case Buffer.ROW_MODIFIED:
                         // restore old value of fields
                         after.findByRowID(new rowid(afterRowid));
                         if (after._available())
                         {
                            Record afterRec = afterDMO.getCurrentRecord();
                            BaseRecord.copy(afterRec, srcBeforeRec, false);
                            afterDMO.peerRowid(new rowid());
                            afterDMO.rowState(Buffer.ROW_UNMODIFIED);
                            afterDMO.originRowid(new rowid());
                            after.release();
                            savedAny = true;
                         }
                         break;

                      case Buffer.ROW_CREATED:
                         // delete back this record
                         after.findByRowID(new rowid(afterRowid));
                         if (after._available())
                         {
                            after.deleteRecord();
                         }
                         break;
                      // otherwise ?
                   }
                   afterDMO.setIgnoreBeforeTracking(false);
                }
             }
             finally
             {
                if (beforeRes != null)
                {
                   beforeRes.close();
                }
             }

             // if everything went fine, delete all data from this table
             deleteAll();

             // and remove the origin-table link
             ((AbstractTempTable) this.getParentTable()).setOriginTable(null);
             ((AbstractTempTable) after.tableHandleNative()).setOriginTable(null);
          }
          catch (PersistenceException exc)
          {
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             commit *= -1;

             // ErrorManager.recordOrShowError(?);
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Error rejecting changed", exc);
             }
             return false;
          }
          finally
          {
             if (savedAny)
             {
                afterDMO.local.nonEmptyTableFlags.add(afterDMO.multiplexID);
             }

             try
             {
                switch (commit)
                {
                   case 1:
                      persistence.commit(Persistence.TEMP_CTX);
                      break;
                   case -1:
                      persistence.rollback(Persistence.TEMP_CTX);
                      break;
                   case 0:
                      break;
                }
             }
             catch (PersistenceException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);

                // ErrorManager.recordOrShowError(?);
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Error rejecting changed", exc);
                }
                return false;
             }
          }

          return true;
       }

       /**
        * Actual implementation of {@code MARK-NEW} method. Called from {@link BufferImpl#markNew()}.
        *
        * @param   before
        *          The before buffer.
        *
        * @return  {@code true} if operation is successful.
        */
       protected boolean markNew(BufferImpl before)
       {
          if (isTableDefinitelyEmpty())
          {
             return true; // nothing to process
          }

          Persistence persistence = getPersistence();
          boolean savedAny = false;
          int commit = 0;   // 0 = do nothing, 1 = commit, -1 = rollback

          TemporaryBuffer beforeBuffer = (TemporaryBuffer) before.buffer();
          try
          {
             commit = persistence.beginTransaction(Persistence.TEMP_CTX) ? 1 : 0;

             FQLExpression fql = new FQLExpression("from ");
             fql.append(getDMOImplementationName());
             fql.append(" where ").append(TemporaryBuffer.MULTIPLEX_FIELD_NAME).append(" = ", true);
             fql.append(" and (_rowState is null or _rowState = 0) order by ");
             fql.append(Session.PK);

             ScrollableResults<Record> beforeRes =
                persistence.scroll(null,
                                   fql.toFinalExpression(),
                                   new Object[] { getMultiplexID() },
                                   0,
                                   0,
                                   ResultSet.TYPE_FORWARD_ONLY);

             try
             {
                while (beforeRes.next())
                {
                   Record srcBeforeRec = (Record) beforeRes.get()[0];

                   before.setUpBeforeBuffer(true);
                   before.create();
                   beforeBuffer.peerRowid(new rowid(srcBeforeRec.primaryKey()));
                   beforeBuffer.rowState(Buffer.ROW_CREATED);
                   before.release();
                   before.setUpBeforeBuffer(false);
                   savedAny = true;
                }
             }
             finally
             {
                if (beforeRes != null)
                {
                   beforeRes.close();
                }
             }

             // then update the flag for the after buffer:
             fql = new FQLExpression("update ");
             fql.append(getDMOImplementationName());
             fql.append(" set _rowState = ?");
             fql.append(" where ").append(MULTIPLEX_FIELD_NAME).append(" = ?");
             fql.append(" and (_rowState is null or _rowState = 0)");
             Object[] args = new Object[] {Integer.toString(Buffer.ROW_CREATED),
                                           Integer.toString(getMultiplexID())};
             persistence.deleteOrUpdate(fql.toFinalExpression(),
                                        Persistence.TEMP_CTX,
                                        args,
                                        getEntityName(),
                                        !isUndoable());
          }
          catch (PersistenceException exc)
          {
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             commit *= -1;

             // ErrorManager.recordOrShowError(?);
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Error marking new", exc);
             }
             return false;
          }
          finally
          {
             if (savedAny)
             {
                beforeBuffer.local.nonEmptyTableFlags.add(beforeBuffer.multiplexID);
             }

             try
             {
                switch (commit)
                {
                   case 1:
                      persistence.commit(Persistence.TEMP_CTX);
                      break;
                   case -1:
                      persistence.rollback(Persistence.TEMP_CTX);
                      break;
                   case 0:
                      break;
                }
             }
             catch (PersistenceException exc)
             {
                DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);

                // ErrorManager.recordOrShowError(?);
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Error marking new", exc);
                }
                return false;
             }
          }

          return true;
       }

       /**
        * Helper method for {@code DataSet} GET-CHANGES. This method copies the difference of records
        * that results from {@code parent-mode} flag of this method.
        * <br>
        * Copies only unchanged records from {@code this} to {@code parentDst} for all changed records
        * from {@code dst}, using an UNIQUE index for iteration. These are normally skipped when
        * {@code parentDst} was populated from {@code parentSrc} but now are required because of the
        * {@code parentMode} flag. Only {@code AFTER} records are copied: since they were NOT modified
        * they do not have a BEFORE pair.
        *
        * @param   parentDst
        *          The buffer that will receive the copied records.
        * @param   parentSrc
        *          The buffer that will receive the copied records.
        * @param   childSrc
        *          The buffer/table that contains the changes in the child table.
        * @param   whereStr
        *          The where predicate that represents the relation between the original buffers, used
        *          for locating parent-records.
        *
        * @return  {@code true} on success and {@code false} if any error is detected.
        */
       protected static boolean copyParentUnchangedRecords(BufferImpl parentDst,
                                                           BufferImpl parentSrc,
                                                           BufferImpl childSrc,
                                                           String whereStr)
       {
          // What this method does:
          //    for each [childSrc.before]
          //        each parentSrc
          //        whereStr
          //      if (parentSrc.state == U)
          //          create parentDst.
          //          copy parentSrc to parentDst

          BufferImpl childSrcBefore = childSrc.beforeBufferNative();
          QueryWrapper query = QueryWrapper.createQueryUnnamedPool();
          query.setBuffers(childSrcBefore, parentSrc);
          StringBuilder sb = new StringBuilder();
          sb.append("FOR EACH ");
          sb.append(childSrcBefore.doGetName());
          sb.append(", EACH ");
          sb.append(parentSrc._name());
          if (whereStr != null && !whereStr.isEmpty())
          {
             sb.append(" ");
             // TODO: this is not very smart! it will fail for particular buffer name cases
             sb.append(whereStr.replace(childSrc.doGetName(), childSrcBefore.doGetName()));
          }
          sb.append(" NO-LOCK");
          try
          {
             query.prepare(sb.toString());
             query.hintFullResults();
             query.queryOpen();
             query.getFirst();

             while (!query._isOffEnd())
             {
                integer rowState = parentSrc.rowState();
                if (rowState.isUnknown() || rowState.intValue() == Buffer.ROW_UNMODIFIED)
                {
                   parentDst.create();
                   Record dstDMO = parentDst.buffer().getCurrentRecord();
                   Record srcDMO = parentSrc.buffer().getCurrentRecord();
                   BaseRecord.copy(dstDMO, srcDMO, false);
                   parentDst.release();
                }
                query._getNext();
             }
          }
          catch (Exception ex)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Failed to copy parent unchanged records", ex);
             }
             ErrorManager.recordOrShowError(
                0, "GET-CHANGES error. Raise log level to WARNING to see details");

             return false;
          }
          finally
          {
             query.queryClose();
             query.delete();
          }

          return true;
       }

       /**
        * If <code>copyTempTableErrorMode</code> is <code>true</code> then dispaly "The caller's
        * temp-table parameter {srcBufName} does not match to the target temp-table {dstBufName}"
        * error message and abend. Else display "COPY-TEMP-TABLE requires {srcBufName} and
        * {dstBufName} columns to match exactly unless the fourth loose-mode parameter is passed as
        * TRUE" error message.
        *
        * @param errorMode
        *        Table copy mode, affects error handling.
        * @param srcBufName
        *        Source buffer name.
        * @param dstBufName
        *        Destination buffer name.
        */
       protected static void handleTablesDoNotMatch(CopyTableMode errorMode,
                                                    String srcBufName,
                                                    String dstBufName)
       {
          NumberedException e;
          switch (errorMode)
          {
             case COPY_TEMP_TABLE_MODE:
                ErrorManager.recordOrShowError(12303, srcBufName, dstBufName);
                // COPY-TEMP-TABLE requires <source-name> and <target-name> columns to match exactly unless the fourth loose-mode parameter is passed as TRUE.
                break;

             case INPUT_PARAM_MODE:
                e = new NumberedException("The caller's temp-table parameter " + srcBufName +
                            " does not match to the target temp-table " + dstBufName, 5363, false);
                ErrorManager.displayError(e.getNumber(), e.getMessage(), e.isPrefix());
                throw new InvalidParameterConditionException(e);

             case OUTPUT_TABLE_PARAM_MODE:
                e = new NumberedException("The caller's temp-table parameter " + dstBufName +
                            " does not match to the target temp-table " + srcBufName, 5363, false);
                ErrorManager.displayError(e.getNumber(), e.getMessage(), e.isPrefix());
                throw new InvalidParameterConditionException(e);

             case OUTPUT_TABLE_HANDLE_PARAM_MODE:
                ErrorManager.displayError(5363,
                      "The caller's temp-table parameter " + dstBufName +
                      " does not match to the target temp-table " + srcBufName, false);
                e = new NumberedException(
                      "Try switching TEMP-TABLE method ADD-FIELDS-FROM for CREATE-LIKE", 9030, false);
                ErrorManager.displayError(e.getNumber(), e.getMessage(), e.isPrefix());
                throw new InvalidParameterConditionException(e);
          }
       }

       /**
        * Make the specified proxy buffer instance mutable, to allow runtime buffer binding.
        *
        * @param    dmoBufIface
        *           The DMO buffer interface.
        * @param    proxy
        *           The DMO proxy.
        *
        * @return   The mutable DMO proxy.
        */
       protected static <T> T makeMutable(Class<T> dmoBufIface, T proxy)
       {
          Class<?>[] interfaces =
          {
             dmoBufIface,
             BufferReference.class,
             TempTableRecord.class
          };

          TemporaryBuffer buffer = (TemporaryBuffer) ((BufferReference) proxy).buffer();
          buffer.initialize(true);

          buffer.mutableHandler = new ReferenceProxy((Temporary) proxy);
          try
          {
             T dmoProxy = ProxyFactory.getProxy(BufferImpl.class,
                                                true,
                                                interfaces,
                                                false,
                                                doNotProxyRes,
                                                () -> new DmoProxyPlugin(dmoBufIface, buffer.getDMOInterface()),
                                                buffer.mutableHandler);

             return dmoProxy;
          }
          catch (Exception e)
          {
             throw new RuntimeException(e);
          }
       }

       /**
        * Create a proxy for the given buffer instance, to act as an instance of the specified type, so it can be
        * passed as an argument to a direct Java method call. Proxy will have as a super class the proxy class from
        * previous level. Super class of the Proxy is the the class of the proxy from previous level.
        * The new proxy class generated will have the following format:
        *
        *  class Proxy extends proxy.getClass()
        *  implements dmoBufIface, BufferReference, TempTableRecord?
        *  {
        *     ...
        *  }
        *
        * @param   <T>
        *          Record type.
        * @param   buffer
        *          Record buffer instance associated with the created proxy.
        * @param   ref
        *          Interface which defines DMO's public API methods.
        *
        * @return  An object which implements the specified interface, and has as a super class the proxy class from
        *          previous level.
        */
       public static <T extends Buffer> T mutableBufferProxy(Buffer ref, Class<T> type)
       {
          Class<?>[] interfaces =
          {
             type,
             BufferReference.class,
             TempTableRecord.class
          };

          TemporaryBuffer buffer = (TemporaryBuffer) ((BufferReference) ref).buffer();
          buffer.initialize(true);

          buffer.mutableHandler = new ReferenceProxy((Temporary) ref);

          try
          {
             T dmoProxy = ProxyFactory.getProxy(ref.getClass(),
                                                true,
                                                interfaces,
                                                false,
                                                doNotProxyRes,
                                                null,
                                                buffer.mutableHandler);

             return dmoProxy;
          }
          catch (Exception e)
          {
             throw new RuntimeException(e);
          }
       }

       /**
        * Update the object reference count.  It will decrement the old value reference and increment the new
        * value reference.
        *
        * @param    dmoIface
        *           The DMO interface.
        * @param    oldVal
        *           The old value.
        * @param    newVal
        *           The new value.
        */
       static void updateObjectRefCount(Class<?> dmoIface, object<?> oldVal, object<?> newVal)
       {
          Context local = context.get();

          if (oldVal != null && !oldVal.isUnknown())
          {
             decrementObjectCountRef(local, dmoIface, oldVal);
          }

          if (newVal != null && !newVal.isUnknown())
          {
             incrementObjectCountRef(local, dmoIface, newVal);
          }
       }

       /**
        * Write the specified TABLE handle to a XML.
        * <p>
        * This is used when the remote call sent the TABLE as XML and it must receive it in the same format
        * (used by SOAP callers).
        *
        * @param    tableHandle
        *           The handle referencing the table.
        * @param    isHandle
        *           Flag indicating that this is used for a DATASET-HANDLE parameter.
        *
        * @return   The XML representation of this table.
        */
       static String writeToXml(handle tableHandle, boolean isHandle)
       {
          if (!tableHandle._isValid())
          {
             return null;
          }

          AbstractTempTable atb = (AbstractTempTable) tableHandle.getResource();
          longchar tableXml = new longchar("");
          atb.writeXml(new TargetData(TargetData.TYPE_LONGCHAR, tableXml),
                      new logical(false), // this must be false
                      null,
                      null,
                      new logical(isHandle ? true : false), // schema is sent only for TABLE-HANDLE
                      null,
                      new logical(false),
                      null);

          return tableXml.getValue();
       }

       /**
        * Read the TABLE from XML.
        * <p>
        * This is used when the remote call sent the TABLE as XML and it must receive it in the same format
        * (used by SOAP callers).
        *
        * @param    xmlTable
        *           The XML serialized table.
        * @param    append
        *           Flag indicating if the read mode is 'append'. Otherwise, is 'empty.
        * @param    table
        *           The handle where to read the table.
        */
       private static void readFromXml(String xmlTable, boolean append, handle table)
       {
          character readMode = new character(append ? "APPEND" : "EMPTY");

          if (!table._isValid())
          {
             TempTableBuilder.create(table);
          }

          AbstractTempTable atb = (AbstractTempTable) table.getResource();
          longchar lc = new longchar();
          longchar.fixCodePage(lc, "utf-8");
          lc.assign(xmlTable);
          atb.readXml(new SourceData("longchar", lc),
                      readMode,
                      new character(),
                      new logical(false));
       }

       /**
        * Decrements the counter-reference for the specified object.
        *
        * @param    local
        *           The context-local instance.
        * @param    dmoIface
        *           The DMO interface.
        * @param    obj
        *           The object whose reference need to be decremented.
        */
       private static void decrementObjectCountRef(Context local, Class<?> dmoIface, object<?> obj)
       {
          if (ObjectOps.hasDestructors(obj.ref()))
          {
             RefCount count = local.ooObjectCounter.get(dmoIface);
             if (count == null || count.get() <= 0)
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Unbalanced counter for stored Object with d'tors");
                }
             }
             else
             {
                count.decrement();
             }
          }

          ObjectOps.decrement(obj.ref());
       }

       /**
        * Increments the counter-reference for the specified object.
        *
        * @param    local
        *           The context-local instance.
        * @param    dmoIface
        *           The DMO interface.
        * @param    obj
        *           The object whose reference need to be decremented.
        */
       private static void incrementObjectCountRef(Context local, Class<?> dmoIface, object<?> obj)
       {
          ObjectOps.increment(obj.ref());

          if (ObjectOps.hasDestructors(obj.ref()))
          {
             RefCount count = local.ooObjectCounter.get(dmoIface);
             if (count == null)
             {
                count = new RefCount();
                local.ooObjectCounter.put(dmoIface, count);
             }
             else
             {
                count.increment();
             }
          }
       }

       /**
        * Bind this instance to the other buffer.  This will force the proxy to return the original's buffer handle
        * and temp-table, even if the buffer is bound.
        *
        * @param    other
        *           The other buffer.
        *
        * @return   The previously bound buffer.
        */
       public Temporary bind(TemporaryBuffer other)
       {
          Temporary old = mutableHandler.bind((Temporary) other.getDMOProxy());
          mutableHandler.forceDefinition = true;
          return old;
       }

       /**
        * Unbind this buffer and re-bind it to its old buffer.
        *
        * @param    old
        *           The previous buffer to which this instance was bound.
        */
       public void unbind(Temporary old)
       {
          mutableHandler.unbind();
          mutableHandler.forceDefinition = false;
          if (old != null)
          {
             mutableHandler.bind(old);
          }
       }

       /**
        * Check if this buffer is associated with a global temp-table.
        *
        * @return   The state of the {@link #global} flag.
        */
       @Override
       public boolean isGlobal()
       {
          return global;
       }

       /**
        * Restores this buffer's state to that which it had at the beginning of
        * the current block.  In particular, this rolls back all the reversible
        * items for the current scope and removes these entries from the rollback
        * dictionaries managed by the <code>BufferManager</code>.
        * <p>
        * Auto-commit buffers are never rolled back.
        *
        * @param   transaction
        *          <code>true</code> if this commit represents the master
        *          transaction rollback;  <code>false</code> if it represents a
        *          sub-transaction rollback.
        */
       @Override
       public void rollback(boolean transaction)
       {
          if (!isUndoable())
          {
             // in case of the no-undo tables, we need to re-apply all the changes
             // recorded during this transaction; so, the empty flag needs to be reset
             // TODO: maybe a restoration mechanism would be better?
             local.nonEmptyTableFlags.add(multiplexID);
          }

          super.rollback(transaction);
       }

       /**
        * Commit the changes made within a subtransaction.  This entails:
        * <ul>
        *   <li>copying all reversible, record-level actions associated with the
        *       current scope to the next higher scope, so rollback capability
        *       for these actions is not lost when the scope is popped;
        *   <li>copying all undoable, property-level changes associated with the
        *       current scope to the next higher scope, so rollback capability
        *       for these actions is not lost when the scope is popped;
        * </ul>
        * <p>
        * Nothing is done if this is a full transaction commit, because no
        * rollback will be possible at the parent scope once the database-level
        * transaction is committed.
        * <p>
        * If this is a full transaction and there are pending bulk actions, force
        * those actions to be executed (i.e. force the delete).
        *
        * @param   transaction
        *          <code>true</code> if this commit represents the master
        *          transaction commit;  <code>false</code> if it represents a
        *          sub-transaction commit.
        */
       /*
       @Override
       public void commit(boolean transaction)
       {
          boolean inChangeScope = inChangeScope();

          List<PersistenceException> errors = null;

          if (inChangeScope)
          {
             errors = new ArrayList<>();

             if (transaction && isUndoable())
             {
                // flush the bulk deleted records which had their multiplex ID
                // changed; this is performed only for undoable tables.
                Set<Reversible> r0 = bufferManager.getReversibles(this, 0, false);
                if (r0 != null && !r0.isEmpty())
                {
                   ReversibleBulkDelete rev = null;
                   try
                   {
                      List<Long> freeIds = new ArrayList<>();

                      Iterator<Reversible> iter = r0.iterator();
                      while (iter.hasNext())
                      {
                         Reversible r = iter.next();

                         if (r instanceof ReversibleBulkDelete)
                         {
                            rev = (ReversibleBulkDelete) r;
                            rev.forceDelete();
                            iter.remove();
                         }
                         else if (r instanceof RecordBuffer.ReversibleDelete)
                         {
                            // collect IDs of reversible deletes
                            freeIds.add(r.getId());
                         }
                      }

                      if (!freeIds.isEmpty())
                      {
                         // reclaim keys from committed deletes
                         reclaimKeys(freeIds);
                      }
                   }
                   catch (PersistenceException exc)
                   {
                      if (LOG.isLoggable(Level.SEVERE))
                      {
                         LOG.log(Level.SEVERE,
                                 describe() + ":  error rolling back undoable bulk delete " + rev,
                                 exc);
                      }

                      errors.add(exc);
                   }
                }
             }
          }

          super.commit(transaction);

          if (inChangeScope)
          {
             // Report or eat errors.
             for (PersistenceException exc : errors)
             {
                ErrorManager.recordOrThrowError(exc);
             }
          }
       }
       */

       /**
        * Respond to a record change event.  Override the parent's implementation
        * to handle bulk delete events.  If our current record no longer exists in
        * the database, we must release it.
        *
        * @param   event
        *          Event which describes the DMO state change.
        *
        * @throws  PersistenceException
        *          if an error occurs executing a query to detect whether current
        *          record still exists.
        */
       @Override
       public void stateChanged(RecordChangeEvent event)
       throws PersistenceException
       {
          Integer srcMPID = event.getSource().getMultiplexID();
          if (!srcMPID.equals(multiplexID))
          {
             return;
          }

          if (event.isInsert())
          {
             local.nonEmptyTableFlags.add(multiplexID);

             super.stateChanged(event);
          }
          else if (event.isBulkDelete())
          {
             if (!isActive())
             {
                return;
             }

             Record dmo = getCurrentRecord();
             if (dmo == null)
             {
                // No release necessary.
                return;
             }

             // We can avoid issuing a query if the event represents a full bulk
             // delete and the deleted multiplex ID matches our own, because we
             // know our record must have been deleted.
             if (event.isFullBulkDelete())
             {
                release(false);
                return;
             }

             // Query whether the buffer's current record still exists.
             Long id = dmo.primaryKey();
             Persistence persistence = getPersistence();
             if (bulkDeleteEventSQL == null)
             {
                bulkDeleteEventSQL = "select tt." + Session.PK + " from " + getTable() + " tt " +
                                     "where tt." + Session.PK + " = ?1";
             }

             Long result = persistence.getSingleSQLResult(bulkDeleteEventSQL,
                                                          Persistence.TEMP_CTX,
                                                          new Object[] { id });
             if (result == null)
             {
                release(false);
             }
          }
          else
          {
             super.stateChanged(event);
          }
       }

       /**
        * Override the superclass method to always return LockType.NONE. Locking
        * is a no-op for temp table records, since each user has exclusive access
        * to their temp table instance.
        *
        * @param  id
        *         Not used.
        *
        * @return LockType.NONE
        */
       @Override
       public LockType getPinnedLockType(Long id)
       {
          return LockType.NONE;
       }

       /**
        * Override the superclass method to do nothing.  Locking is a no-op for
        * temp table records, since each user has exclusive access to their temp
        * table instance.
        *
        * @param   id
        *          Not used.
        * @param   pinnedLockType
        *          Not used.
        */
       @Override
       public void setPinnedLockType(Long id, LockType pinnedLockType)
       {
       }

       /**
        * Returns TABLE-HANDLE for the buffer.
        *
        * @return TABLE-HANDLE for the buffer.
        */
       @Override
       public handle tableHandle()
       {
          TempTable tt = tableHandleResource();

          return tt == null ? new handle() : new handle(tt);
       }

       /**
        * Get the associated temp-table (for tempporary buffers).
        *
        * @return   The TABLE-HANDLE resource for the buffer.
        */
       @Override
       public TempTable tableHandleResource()
       {
          if (master != null)
          {
             return master.tableHandleResource();
          }

          return tempTableRef;
       }

       /**
        * Initialize the core data on which this buffer relies.  This should be
        * called only once per buffer instance, when the buffer's scope is first
        * opened.
        *
        * @return  {@code true} if the buffer was initialized with this call;
        *          {@code false} if the buffer had been initialized with a
        *          previous call to this method.
        *
        * @throws  IllegalArgumentException
        *          if no implementation class is found for the given database and
        *          interface.
        * @throws  RuntimeException
        *          if the backing DMO implementation class does not implement a
        *          constructor which accepts a multiplex ID.
        */
       @Override
       public boolean initialize()
       {
          return initialize(false);
       }

       /**
        * Initialize the core data on which this buffer relies.  This should be
        * called only once per buffer instance, when the buffer's scope is first
        * opened.
        *
        * @param   fromDefine
        *          {@code true} to acknowledge that this is an early initialization from a define statement
        *          and the scope shouldn't have been opened before-hand.
        *
        * @return  {@code true} if the buffer was initialized with this call;
        *          {@code false} if the buffer had been initialized with a
        *          previous call to this method.
        *
        * @throws  IllegalArgumentException
        *          if no implementation class is found for the given database and
        *          interface.
        * @throws  RuntimeException
        *          if the backing DMO implementation class does not implement a
        *          constructor which accepts a multiplex ID.
        */
       @Override
       public boolean initialize(boolean fromDefine)
       {
          if (global && master != null && master.isDynamic() && !isDynamic())
          {
             // if this is not a dynamic buffer and a master is set, then it means this is a bound buffer to some
             // other buffer; the 'global' flag needs to be false, otherwise TransactionManager$WorkArea.globalBlock
             // will leak Multiplexer instances in the 'finalizables' list.
             global = false;
          }

          if (!super.initialize(fromDefine))
          {
             return false;
          }

          if (!DynamicTablesHelper.isDynamicTableBuffer(this))
          {
             BufferReference tempBuffer = getDMOProxy();
             if (master == null)
             {
                tempTableRef = new StaticTempTable((Temporary) tempBuffer);
                bufferManager.registerPendingStaticTempTable((StaticTempTable) tempTableRef, global);
             }
             else
             {
                tempTableRef = master.tempTableRef;
             }

             ((AbstractTempTable) tempTableRef).registerBuffer((Buffer) tempBuffer);
          }

          Class<? extends Record> dmoCls = getDMOImplementationClass();
          if (!TempRecord.class.isAssignableFrom(dmoCls))
          {
             String msg = "Invalid temporary table DMO:  " + dmoCls.getName() + ".";
             throw new RuntimeException(msg);
          }

          @SuppressWarnings("unchecked")
          Class<? extends TempRecord> tempCls = (Class<? extends TempRecord>) dmoCls;

          try
          {
             dmoCtor = tempCls.getDeclaredConstructor(Integer.class);
          }
          catch (NoSuchMethodException exc)
          {
             String msg = "Invalid temporary table DMO:  " + dmoCls.getName() +
                          " does not implement a multiplex constructor";

             if (LOG.isLoggable(Level.SEVERE))
             {
                LOG.log(Level.SEVERE, describe() + ":  " + msg, exc);
             }

             throw new RuntimeException(msg, exc);
          }

          return true;
       }

       /**
        * Read all rows from the virtual temp-table backing this buffer (i.e., only those rows with
        * this buffer's multiplex ID) and allow the given row handler to process each found row. Rows
        * are queried according to the primary index on the table, or in ascending order of primary
        * key, in the absence of a primary index.
        *
        * @param   rowHandler
        *          Consumer function which will handle each result row according to the needs of the
        *          caller.
        * @return  number of processed rows
        * @throws  ErrorConditionException
        *          if there is any error querying the rows in the table.
        */
       public int readAllRows(Consumer<TempRecord> rowHandler)
       {
          int nr = 0;
          if (isTableDefinitelyEmpty())
          {
             // nothing to do
             return nr;
          }

          initialize();

          Persistence persistence = getPersistence();

          try
          {
             FQLExpression buf = new FQLExpression("from ");
             buf.append(getDMOImplementationName());

             String tableAlias = getDMOAlias();
             buf.append(" as ").append(tableAlias);

             // srcBuf is a TemporaryBuffer, so it isMultiplexed()
             buf.append(" where ");
             buf.append(MULTIPLEX_FIELD_NAME);
             buf.append(" = ", true);

             // force the records to be ordered by their primary index (or by ID if none defined)
             String orderBy = getPrimaryOrderByClause(tableAlias);
             buf.append(orderBy);

             String fql = buf.toFinalExpression();
             Object[] parms = new Object[] { getMultiplexID() };
             ScrollableResults<TempRecord> results =
                   persistence.scroll(null, fql, parms, 0, 0, ResultSet.TYPE_FORWARD_ONLY);

             BufferManager bufMgr = getBufferManager();

             // process results
             while (results.next())
             {
                nr++;
                Object[] row = results.get();
                TempRecord dmo = (TempRecord) row[0];
                rowHandler.accept(dmo);
                bufMgr.evictDMOIfUnused(persistence, getPersistenceContext(), dmo);
             }
          }
          catch (PersistenceException exc)
          {
             DBUtils.handleException(DatabaseManager.TEMP_TABLE_DB, exc);
             ErrorManager.recordOrThrowError(exc);
          }
          return nr;
       }

       /**
        * Get code page of the specified CLOB field.
        *
        * @param   property
        *          The ORM property name of the CLOB field.
        *
        * @return  The code page of the specified CLOB field. If not configured, the CPINTERNAL is returned.
        */
       @Override
       public String getCodePage(String property)
       {
          String cp = tempTableRef.getCodePage(property);
          return (cp == null) ? I18nOps._getCPInternal() : cp;
       }

       /**
        * Counts the number of records in the TEMP-TABLE behind this buffer. This implementation is
        * designed to be (much) faster than the aggregate equivalent methods but it does not support
        * any filtering predicates.
        * <p>
        * This is a SILENCED method. If a persistence error occur, -1 is returned and NO exception is
        * thrown. In fact the method was created to be used internally, by FWD.
        *
        * @return  the number of records in the TEMP-TABLE associated with this buffer. -1 is returned
        *          if any issue is encountered.
        */
       public int count()
       {
          if (isTableDefinitelyEmpty())
          {
             return 0; // evidently
          }

          try
          {
             Object[] args = new Object[] {getMultiplexID()};
             String sql = "select count(*) from " + getTable() +
                          " where " + MULTIPLEX_FIELD_NAME + " = ?";
             Long res = getPersistence().getSingleSQLResult(sql, Persistence.TEMP_CTX, args);
             return res.intValue();

          }
          catch (Exception ex)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Failed to count records from " + getTable(), ex);
             }
             return -1;
          }
       }

       /**
        * Override the superclass method to do nothing.  Locking is a no-op for
        * temp table records, since each user has exclusive access to their temp
        * table instance.
        *
        * @throws  PersistenceException
        *          never.
        */
       @Override
       public final void upgradeLock()
       throws PersistenceException
       {
       }

       /**
        * Indicate whether this is a temp-table buffer and we are not currently within an existing
        * database transaction.
        *
        * @return  {@code true} if auto-commit, else {@code false}.
        */
       @Override
       public boolean isAutoCommit()
       {
          return !persistenceContext.isTransactionOpen();
       }

       /**
        * Convenience method to indicate whether this buffer is backed by a
        * temporary table.
        *
        * @return  <code>true</code>.
        */
       @Override
       public boolean isTemporary()
       {
          return true;
       }

       /**
        * Check whether this instance was defined as REFERENCE-ONLY.
        *
        * @return  {@code true} if the table was defined as REFERENCE-ONLY.
        */
       public boolean referenceOnly()
       {
          return referenceOnly;
       }

       /**
        * Get the implicit SQL index name.
        *
        * @param   dmoInterface
        *          DMO interface which represents the temp table.
        *
        * @return  See the local method implementation.
        */
       public String getImplicitSqlIndexName(Class<? extends DataModelObject> dmoInterface)
       {
          return local.getImplicitSqlIndexName(dmoInterface);
       }

       /**
        * Get the implicit index name.
        *
        * @param   dmoInterface
        *          DMO interface which represents the temp table.
        *
        * @return  See the local method implementation.
        */
       public String getImplicitIndexName(Class<? extends DataModelObject> dmoInterface)
       {
          return local.getImplicitIndexName(dmoInterface);
       }

       /**
        * Get the logical database name associated with this buffer.  This will
        * always be the standard name for the temp table database as defined in
        * {@link DatabaseManager#TEMP_TABLE_DB}, regardless of the value of the
        * <code>ldbOrAlias</code> parameter.
        *
        * @param   ldbOrAlias
        *          A logical database name or database alias.
        *
        * @return  Standard, temp table database name.
        */
       @Override
       protected String getLDBName(String ldbOrAlias)
       {
          return DatabaseManager.TEMP_TABLE_SCHEMA;
       }

       /**
        * Get the database information object associated with this buffer.  This
        * will always be the standard temp table database as defined in
        * {@link DatabaseManager#TEMP_TABLE_DB}, regardless of the value of the
        * <code>ldbName</code> parameter.
        *
        * @param   ldbName
        *          A logical database name.
        *
        * @return  Standard, temp table database.
        */
       @Override
       protected Database getPDB(String ldbName)
       {
          return DatabaseManager.TEMP_TABLE_DB;
       }

       /**
        * Perform any actions needed when this buffer's outermost scope opens (or if the buffer was not active
        * when that scope was opened, when the buffer is first activated thereafter).
        * <p>
        * This implementation overrides the parent method to do nothing. The parent implementation notifies the
        * connection manager that this buffer is active, but accesses to the temp table database are outside of
        * the connection manager's responsibilities.
        */
       @Override
       protected void onOpenOutermostScope()
       {
       }

       /**
        * Perform any actions needed when this buffer's outermost scope closes.
        * <p>
        * This implementation overrides the parent method to not interact with the connection manager. The parent
        * implementation notifies the connection manager that this buffer is no longer active, but accesses to
        * the temp table database are outside of the connection manager's responsibilities.
        */
       @Override
       protected void onCloseOutermostScope()
       {
          Buffer buf;
          if (tempTableRef != null && (buf = (Buffer) getDMOProxy()) != null)
          {
             ((AbstractTempTable) tempTableRef).deregisterBuffer(buf);
          }
       }

       /**
        * Create a new instance of the DMO implementation class associated with
        * this buffer, and assign this buffer's multiplex ID to it.  This method
        * must not be invoked before a buffer scope has been opened on this
        * buffer.
        *
        * @return  A transient DMO instance.
        *
        * @throws  InstantiationException
        *          if the class cannot be instantiated.
        * @throws  IllegalAccessException
        *          if the DMO class' target constructor cannot be accessed.
        * @throws  InvocationTargetException
        *          if the underlying constructor throws an exception.
        */
       @Override
       protected TempRecord instantiateDMO()
       throws InstantiationException,
              IllegalAccessException,
              InvocationTargetException
       {
          if (multiplexID == null)
          {
             throw new IllegalStateException("Cannot create a new record before buffer scope is opened");
          }

          TempRecord dmo = (TempRecord) dmoCtor.newInstance(multiplexID);

          if (!isUndoable())
          {
             dmo.updateState(null, DmoState.NOUNDO, true);
          }

          return dmo;
       }

       /**
        * Look up the number of fields in the legacy table represented by this buffer.
        *
        * @return  Number of fields.
        */
       @Override
       protected int lookupNumFields()
       {
          return TableMapper.getNumFields(tempTableRef);
       }

       /**
        * Indicate whether the table backing this buffer is known to either: (a) not exist; or (b) contain no
        * rows.
        * <p>
        * This implementation involves only the virtual table backing the current buffer (i.e., whose multiplex
        * ID matches that of this buffer), not the entire physical table.
        *
        * @return  {@code false} to indicate the backing table does not exist or is known to be empty; {@code
        *          true} if the table exists and might have rows.
        */
       @Override
       protected boolean isTableDefinitelyEmpty()
       {
          boolean hasRecords = local.nonEmptyTableFlags.contains(multiplexID);

          return !hasRecords;
       }

       /**
        * Indicate whether the table backing this buffer contains records. If this can't be inferred easily,
        * this returns null.
        *
        * @return  {@code true} if the table surely has records, {@code false} if the table surely doesn't have
        *          records, {@code null} if this can't be computed wihout actually querying the database.
        */
       @Override
       public Boolean fastHasRecords()
       {
          Session session = getSession();
          if (session == null)
          {
             return null;
          }

          try
          {
             return DirectAccessHelper.hasRecords(session, getTable(), getMultiplexID());
          }
          catch (DirectAccessException e)
          {
             if (LOG.isLoggable(Level.WARNING) && !e.isExpected())
             {
                LOG.log(Level.WARNING, "Failed fastHasRecords with direct-access due to " + e.getMessage());
             }
             return null;
          }
       }

       /**
        * Indicate whether changes made to records managed by this buffer can be undone during a
        * rollback.
        * <p>
        * We override this method for temp-tables, which can be marked NO-UNDO when defined. There
        * also is a directory configuration option to treat all temp-tables as NO-UNDO, regardless
        * of how they originally were defined.
        *
        * @return  {@code true} if record changes can be undone, else {@code false}.
        */
       @Override
       protected boolean isUndoable()
       {
          if (DatabaseManager.isForceNoUndoTempTables())
          {
             if (undoStateProvider != null && undoStateProvider.isUndoable())
             {
                // log a warning: we are running in force-no-undo mode, but we have encountered a temp-table
                // which was not marked NO-UNDO
                String table = getTable();
                if (LOG.isLoggable(Level.WARNING) && !forceNoUndoConflicts.contains(table))
                {
                   boolean doLog;

                   // only log once per table to avoid flooding the log
                   synchronized (forceNoUndoConflicts)
                   {
                      doLog = forceNoUndoConflicts.add(table);
                   }

                   if (doLog)
                   {
                      String legacyName = this.getLegacyName();
                      String msg = "Temp-table '" + legacyName + "' (" + table + ")" +
                                   " is defined undoable, but is being forced to NO-UNDO by global NO-UNDO mode";
                      LOG.log(Level.WARNING, msg);
                      if (LOG.isLoggable(Level.FINE))
                      {
                         // log stack trace as an aid to finding this temp-table in business logic
                         LOG.log(Level.FINE, "Location:", new Throwable());
                      }
                   }
                }
             }

             return false;
          }
          else if (undoStateProvider != null)
          {
             return undoStateProvider.isUndoable();
          }

          // Normally, this exception will never be thrown.
          // * in the case of dynamic temp-table, the UndoStateProvider is configured in constructor
          //    by the TempTableBuilder and passed directly
          // * in the case of static temp-tables, the StaticTempTable is configured later,
          //    in openScope() of the buffer. This method cannot be called before the scope is open.
          throw new IllegalStateException("UndoStateProvider was not yet configured");
       }

       /**
        * Get the multiplex ID of this temporary buffer.  This will be
        * <code>null</code> if this method is invoked before a buffer scope is
        * opened on this buffer for the first time.  Otherwise, it will be a
        * unique identifier within the current context.
        *
        * @return  Multiplex ID for this temporary buffer.
        */
       @Override
       public Integer getMultiplexID()
       {
          return multiplexID;
       }

       /**
        * Indicate whether the backing table for this buffer is multiplexed;  that
        * is, whether the backing table represents multiple, virtual tables by
        * using a multiplex ID to differentiate table segments.
        * <p>
        * Temp tables always are multiplexed.
        *
        * @return  <code>true</code>.
        */
       @Override
       protected boolean isMultiplexed()
       {
          return true;
       }

       /**
        * Indicate whether this buffer implementation allows bulk actions.
        *
        * @return  <code>true</code>.
        */
       @Override
       protected boolean allowsBulkActions()
       {
          return true;
       }

       /**
        * Retrieve the master record buffer for this buffer.
        * <p>
        * This only has meaning for shared temp tables.  For a temp table defined
        * as NEW SHARED, this buffer is returned.  For a temp table defined as
        * SHARED, the domain master buffer is returned.
        *
        * @return  The master buffer.
        */
       @Override
       protected RecordBuffer getMasterBuffer()
       {
          return (master != null ? master : this);
       }

       /**
        * Gets the legacy name assigned to a dynamically prepared temp-table.
        *
        * @return  The name assigned by TEMP-TABLE-PREPARE, or <code>null</code> if this buffer
        *          represents a static temp-table.
        */
       @Override
       protected String getDynamicName()
       {
          if (!isDynamic())
          {
             return null;
          }

          character name = tempTableRef.name();
          if (name == null || name.isUnknown())
          {
             return null;
          }

          return name.toStringMessage();
       }

       /**
        * Open a new buffer scope for this record buffer.  Open a new multiplex
        * scope and assign a multiplex ID the first time we are invoked for a
        * buffer instance.
        */
       @Override
       protected void openScope()
       {
          BufferManager.registerScopeable(bufferManager);

          if (referenceOnly)
          {
             return;
          }

          super.openScope();

          // only need to process if we are opening the outermost scope for this buffer; nested
          // scopes need not apply
          if (getOpenScopeCount() == 1)
          {
             // a temporary buffer can be initialized as soon as its first scope is opened, because
             // the _temp database is permanently connected
             initialize();

             // force calculation of this buffer's type (before or after); this must not be delayed
             // because otherwise another temp-table with the same name may be added on the stack,
             // hiding its definition - so we force this when its outermost scope is opened
             ((BufferImpl) getDMOProxy()).computeBufferType();

             // if this is the first time this table is being used, create it in the database
             if (openMultiplexScope())
             {
                local.createTable(getDMOInterface(), getTable());
             }

             // map legacy info (this is tolerant to being called more than once)
             TableMapper.mapTemporaryTable(tempTableResource());

             // register for temp table cleanup
             Multiplexer multiplexer = new Multiplexer(persistenceContext);
             if (global && (master == null || isDynamic()))
             {
                // a global temp table buffer is cleaned up when the context ends
                txHelper.registerFinalizable(multiplexer, true);

                if (isDynamic())
                {
                   dynamicMultiplexer = multiplexer;
                }
             }
             else
             {
                // a non-global temp table buffer is cleaned up when the top level method scope ends
                // (this corresponds with the nearest enclosing top level procedure, function, or trigger)
                pm.executeOnReturn(multiplexer::finished, multiplexer.weight());
             }

             // register for no-undo validation if outside an application transaction
             if (!isUndoable() && !bufferManager.isTransaction())
             {
                NoUndoValidator val = new NoUndoValidator();
                pm.executeOnReturn(val::finished, val.weight());
             }

             processCodePages();
          }
       }

       /**
        * Open a new buffer scope for this record buffer at the given block depth. Open a new
        * multiplex scope and assign a multiplex ID the first time we are invoked for a buffer
        * instance.
        *
        * @param   blockDepth
        *          Zero-based depth of the block (starting from the outermost scope) at which the
        *          buffer scope is opened.
        */
       @Override
       protected void openScopeAt(int blockDepth)
       {
          BufferManager.registerScopeable(bufferManager);

          if (referenceOnly)
          {
             return;
          }

          super.openScopeAt(blockDepth);

          initialize();

          // if this is the first time this table is being used, create it in the database
          if (openMultiplexScope())
          {
             local.createTable(getDMOInterface(), getTable());
          }

          // map legacy info (this is tolerant to being called more than once)
          TableMapper.mapTemporaryTable(tempTableResource());

          // register for temp table cleanup
          Multiplexer multiplexer = new Multiplexer(persistenceContext);
          txHelper.registerFinalizableAt(blockDepth, multiplexer);
          if (isDynamic() && blockDepth == 0)
          {
             dynamicMultiplexer = multiplexer;
          }

          // register for no-undo validation if outside an application transaction
          // TODO: doing this global scope creates a memory leak; does it even make sense to register
          // these at a scope other than the current scope?
          if (blockDepth > 0 && !isUndoable() && !bufferManager.isTransaction())
          {
             txHelper.registerFinalizableAt(blockDepth, new NoUndoValidator());
          }
       }

       /**
        * Check if this buffer is referenced by other {@link #explicitBuffers buffers}.
        *
        * @return   See above.
        */
       protected boolean hasExplicitBuffers()
       {
          return explicitBuffers != null && !explicitBuffers.isEmpty();
       }

       /**
        * Deregister a dynamic multiplexer object from the transaction manager's global finalizables
        * when this buffer goes out of scope and invoke the multiplexer's scope closure processing.
        */
       @Override
       protected void resourceDeleted()
       {
          super.resourceDeleted();

          if (master != null)
          {
             master.explicitBuffers.remove(mutableProxy);
          }

          if (mutableHandler != null             &&
              mutableHandler.autoDeleteBinding   &&
              mutableHandler.boundBuffer != null &&
              mutableHandler.boundBuffer != this)
          {
             // bound buffers created internally by FWD need to be automatically deleted.
             mutableHandler.boundBuffer.resourceDeleted();
          }

          if (master == null || dynamicMultiplexer != null)
          {
             closeMultiplexScope();

             if (dynamicMultiplexer != null)
             {
                // remove multiplexer from TransactionManager's global finalizables, unless we are being
                // invoked in the global scope, since it will be removed imminently anyway
                int currentScope = bufferManager.getOpenBufferScopes();
                if (currentScope > 0)
                {
                   txHelper.deregisterGlobalFinalizable(dynamicMultiplexer);
                }
             }
          }
       }

       /**
        * Override the superclass method to do nothing.  Locking is a no-op for
        * temp table records, since each user has exclusive access to their temp
        * table instance.
        */
       @Override
       protected void initPinnedLockTypes()
       {
       }

       /**
        * Perform a bulk delete of all records in the table associated with this buffer.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the bulk delete operation.
        */
       @Override
       protected void deleteAll()
       {
          try
          {
             if (!forceLoopingDelete())
             {
                removeRecords(null, null, null);
             }
             else
             {
                loopDelete(null, null, null);
             }
          }
          catch (PersistenceException exc)
          {
             ErrorManager.recordOrThrowError(exc);
          }
       }

       /**
        * Get all slave buffers associated with this buffer.  If there are no slave buffers, <code>null</code> is
        * returned.
        *
        * @return   See above.
        */
       protected Set<TemporaryBuffer> getAllSlaveBuffers()
       {
          Map<TemporaryBuffer, Object> slaves = local.domains.get(this);
          if (slaves == null)
          {
             return null;
          }

          Set<TemporaryBuffer> buffers = new HashSet<>();
          buffers.addAll(slaves.keySet());

          return buffers;
       }

       /**
        * Remove all the domains associated with this buffer.
        */
       protected void cleanupSlaveBuffers()
       {
          local.domains.remove(this);
          this.setDeleted(true);
       }

       /**
        * Decrements the counter-reference for the specified object.
        *
        * @param   obj
        *          The object whose reference need to be decremented.
        */
       @Override
       protected void decrementObjectCountRef(object<?> obj)
       {
          if (obj == null || obj.isUnknown())
          {
             return;
          }

          decrementObjectCountRef(local, getDMOInterface(), obj);
       }

       /**
        * Get the mappings of property names, from definition to bound buffer.
        *
        * @return   The {@link ReferenceProxy#properties} mappings.
        */
       @Override
       protected Map<String, String> getBoundPropertyMappings()
       {
          return mutableHandler == null ? null : mutableHandler.properties;
       }

       /**
        * Gets the current value of the {@code ROW-STATE} property for this record. Valid values
        * are defined as constants in {@link Buffer} interface. Additionally, {@code unknown} value is
        * returned if property is not available.
        *
        * @return  The current value of the {@code ROW-STATE} property for this record as explained above.
        */
       @Override
       protected integer rowState()
       {
          if (!isAvailable() /*|| ((BufferImpl) getDMOProxy())._dataSet() == null*/)
          {
             return new integer();
          }

          TempRecord record = getCurrentRecord();
          Integer state = record._rowState();
          return new integer(state == null ? Buffer.ROW_UNMODIFIED : state);
       }

       /**
        * Sets the value of the {@code ROW-STATE} property for this record. Valid values are defined
        * as constants in {@link Buffer} interface. Additionally {@code null} value is allowed if
        * property is not available.
        *
        * @param  state
        *         The new value of the {@code ROW-STATE} property for this record as explained above.
        */
       @Override
       public void rowState(Integer state)
       {
          if (!isAvailable())
          {
             return;
          }

          if (state != null &&
              state != Buffer.ROW_UNMODIFIED &&
              state != Buffer.ROW_DELETED &&
              state != Buffer.ROW_MODIFIED &&
              state != Buffer.ROW_CREATED)
          {
             LOG.log(Level.SEVERE, "Internal: invalid ROW-STATE constant: " + state);
             return;
          }

          setupBeforeFlagImpl((r) -> r._rowState(state),
                              Buffer.ROW_STATE_FIELD,
                              Buffer.__ROW_STATE__,
                              false);
       }

       /**
        * Gets {@code rowid} of the peer record. Exclusively, for a BEFORE-TABLE record this is the
        * {@code after-rowid} and {@code before-rowid} for AFTER-TABLE record.
        *
        * @return  The rowid value of the peer record or {@code unknown} if there is none.
        */
       @Override
       protected rowid peerRowid()
       {
          if (!isAvailable())
          {
             return new rowid();
          }

          TempRecord record = getCurrentRecord();

          return new rowid(record._peerRowid());
       }

       /**
        * Sets the {@code rowid} value of the peer record. Exclusively, for a BEFORE-TABLE record this
        * is the {@code after-rowid} and {@code before-rowid} for AFTER-TABLE record.
        *
        * @param   peer
        *          The new long rowid value of the peer record or {@code null}.
        */
       @Override
       public void peerRowid(rowid peer)
       {
          setupBeforeFlagImpl((r) -> r._peerRowid(peer.getValue()),
                              Buffer.AFTER_ROWID_FIELD,
                              Buffer.__AFTER_ROWID__,
                              false);
       }

       /**
        * Sets the {@code origin-rowid} for AFTER-TABLE record.
        *
        * @param   peer
        *          The new long rowid value of the record or {@code null}.
        */
       @Override
       public void originRowid(rowid peer)
       {
          setupBeforeFlagImpl((r) -> r._peerRowid(peer.getValue()),
                              Buffer.ORIGIN_ROWID_FIELD,
                              Buffer.__ORIGIN_ROWID__,
                              false);
       }

       /**
        * Sets the {@code datasource-rowid} for AFTER-TABLE record.
        *
        * @param   sourceRowid
        *          The new long rowid value of the record or {@code null}.
        */
       @Override
       public void datasourceRowid(rowid sourceRowid)
       {
          setupBeforeFlagImpl((r) -> r._peerRowid(sourceRowid.getValue()),
                              Buffer.DATA_SOURCE_ROWID_FIELD,
                              Buffer.__DATA_SOURCE_ROWID__,
                              false);
       }

       /**
        * Gets {@code errorFlag} of this record.
        *
        * @return  the {@code errorFlag} of this record or {@code null} if it was not configured. If not null,
        *          the value is a bitwise combination of ERROR and REJECTED attribute. It's up to the called to
        *          extract the bit(s) it needs.
        *
        * @see     Buffer#__ERROR_FLAG__
        * @see     Buffer#ERROR_ERROR
        * @see     Buffer#ERROR_REJECTED
        */
       @Override
       protected Integer errorFlags()
       {
          if (!isAvailable())
          {
             return null;
          }

          return getCurrentRecord()._errorFlags();
       }

       /**
        * Sets or removes the {@code errorFlag} value this peer record. The {@code error} attribute of the record
        * is composed of multiple bit flags. This method manages its value based on the parameters it receives.
        *
        * @param   errFlag
        *          The error bit to be set or removed.
        * @param   set
        *          Use {@code true} to set the flag and {@code false} to remove it.
        *
        * @see     Buffer#__ERROR_FLAG__
        * @see     Buffer#ERROR_ERROR
        * @see     Buffer#ERROR_REJECTED
        */
       @Override
       protected void updateErrorFlags(int errFlag, boolean set)
       {
          integer rowState = rowState();
          boolean updatePeer = !rowState.isUnknown() && rowState.intValue() != Buffer.ROW_DELETED;
          setupBeforeFlagImpl(
             r -> {
                Integer errFlags = r._errorFlags();
                if (errFlags == null)
                {
                   if (set)
                   {
                      r._errorFlags(errFlag);
                   }
                   // else let it unchanged (null)
                }
                else
                {
                   r._errorFlags(set ? (errFlags | errFlag) : (errFlags & ~errFlag));
                }
             },
             Buffer.ERROR_FLAG_FIELD,
             Buffer.__ERROR_FLAG__,
             updatePeer);
       }

       /**
        * Gets {@code errorString} of this record.
        *
        * @return  the {@code errorFlag} of this record or {@code unknown} if it was not configured.
        *
        * @see Buffer#__ERROR_STRING__
        */
       @Override
       protected character errorString()
       {
          if (!isAvailable())
          {
             return new character();
          }

          return new character(getCurrentRecord()._errorString());
       }

       /**
        * Sets the {@code error string} of this record.
        *
        * @param   string
        *          The new {@code error string} of this record.
        *
        * @see Buffer#__ERROR_STRING__
        */
       @Override
       protected void errorString(Text string)
       {
          integer rowState = rowState();
          boolean updatePeer = !rowState.isUnknown() && rowState.intValue() != Buffer.ROW_DELETED;
          setupBeforeFlagImpl((r) -> r._errorString(string.toJavaType()),
                              Buffer.ERROR_STRING_FIELD,
                              Buffer.__ERROR_STRING__,
                              updatePeer);
       }

       /**
        * Set reference to the parent temp table.
        *
        * @param   tempTableRef
        *          Parent temp table.
        */
       protected void setTempTableRef(TempTable tempTableRef)
       {
          this.tempTableRef = tempTableRef;
          if (undoStateProvider instanceof DefaultUndoState)
          {
             // if undoStateProvider is a default one, replace it with proper one
             undoStateProvider = tempTableRef;
          }

          ((AbstractTempTable) tempTableRef).registerBuffer((Buffer) getDMOProxy());
       }

       /**
        * Close multiplex scope and mark the underlying table to be dropped when the session closes.
        */
       protected void closeMultiplexScope()
       {
          if (referenceOnly)
          {
             return;
          }

          if (!isMultiplexScopeClosed())
          {
             try
             {
                doCloseMultiplexScope();
             }
             catch (PersistenceException exc)
             {
                ErrorManager.recordOrThrowError(exc);
             }
          }
       }

       /**
        * Copy output data for the linked table parameter, if any.
        *
        * @param    outputParameter
        *           The table parameter where to copy the data.
        */
       protected void performOutputCopy(TableParameter outputParameter)
       {
          if (!outputParameters.contains(outputParameter))
          {
             // was already processed (by i.e. scope close)
             return;
          }

          Temporary tempDMO = (Temporary) getDMOProxy();
          if (!outputParameter.isRemoteParameter())
          {
             rowid srcRec = (referenceOnly || outputParameter.isByReference()) && isAvailable()
                               ? rowID()
                               : null;

             if (outputParameter.getTable() != null)
             {
                boolean res = copyAllRows(srcRec,
                                          tempDMO,
                                          outputParameter.getTable(),
                                          outputParameter.isAppend(),
                                          CopyTableMode.OUTPUT_TABLE_PARAM_MODE);
                if (!res)
                   deferCopyError();
             }
             else
             {
                // create a copy of this temp-table and assign it to this handle
                TempTableBuilder.create(outputParameter.getTableHandle());
                TempTableBuilder ttDst = (TempTableBuilder) outputParameter.getTableHandle().getResource();
                TempTable tSrc = (((BufferReference) tempDMO).buffer()).tableHandleResource();
                handle htSrc = new handle(tSrc);
                ttDst.createLike(htSrc);
                ttDst.tempTablePrepare(tSrc.name());
                logical res = ttDst.copyTempTable(htSrc);
                if (!res.booleanValue())
                {
                   deferCopyError();
                }
             }
          }
          else
          {
             if (outputParameter.getResultSet().isAsXml())
             {
                TableWrapper wrapper = outputParameter.getResultSet();
                handle tth = ((Buffer) tempDMO).tableHandle();
                String tableXml = writeToXml(tth, wrapper.isTableHandle());
                wrapper.setXmlTable(tableXml);
                return;
             }

    //         try
    //         {
                TempTableResultSet resultSet = new TempTableResultSet(tempDMO,
                                                                      false,
                                                                      true,
                                                                      outputParameter.isAppend());
                TableWrapper tableWrapper = new TableWrapper(resultSet);
                tableWrapper.init(getParentTable());
                outputParameter.setResultSet(tableWrapper);
    //         }
    //         catch (ClassNotFoundException e)
    //         {
    //            deferCopyError();
    //         }
          }

          outputParameters.remove(outputParameter);
       }

       /**
        * Do clean-up in case the buffer runs out of (outermost) scope. This should be called from places responsible of
        * the buffer lifecycle (e.g. {@link #finishedImpl()}).
        */
       protected void cleanup()
       {
          super.cleanup();

          if (this.master == null)
          {
             // this is the default buffer of the table. The domains will get cleared anyway as
             // the table will be out-of-scope soon.
             return;
          }

          Map<TemporaryBuffer, Object> slaves = local.domains.get(this.master);

          if (slaves == null)
          {
              // If the master buffer was already deleted,
              // then it is expected not to find any slave buffers in the domains for this.master.
              // In this case just return normally.
             if (LOG.isLoggable(Level.WARNING) && !this.master.isDeleted())
             {
                LOG.log(Level.WARNING, "Non-consistent domains collection. "
                         + "Can't clean-up a slave buffer properly as there is no slaves collection");
             }

             return;
          }

          slaves.remove(this);
       }

       /**
        * Activate or deactivate the CHANGE-TRACKING for this buffer. When set to {@code true} this
        * flag makes the change-tracking of this dataset AFTER-BUFFER ignore changes and skip storing
        * changes to associated BEFORE-BUFFER. This is needed by REJECT-CHANGES when CHANGE-TRACKING
        * is still on.
        *
        * @param   ignore
        *          {@code true} to deactivate the changes-tracking for this buffer.
        */
       private void setIgnoreBeforeTracking(boolean ignore)
       {
          ignoreBeforeTracking = ignore;
       }

       /**
        * Check whether in this temp-table there are any objects that have pending destructors to be
        * called in case they are deleted.
        *
        * @return  {@code true} when there is at least one object store in this temp-table that has at
        *          least one active destructor.
        */
       private boolean hasOoDestructors()
       {
          RefCount count = local.ooObjectCounter.get(getDMOInterface());

          return count != null && count.get() > 0;
       }

       /**
        * Register code page expression for the CLOB field. Code page will be evaluated and applied in
        * {@link #openScope()}.
        *
        * @param property
        *        Hibernate property name of the CLOB field.
        * @param codePageExpr
        *        Code page expression.
        */
       private void registerCodePage(String property, Supplier codePageExpr)
       {
          if (registeredCodePages == null)
          {
             registeredCodePages = new HashMap<>();
          }

          registeredCodePages.put(property, codePageExpr);
       }

       /**
        * Set code pages for the CLOB fields of the parent {@link TempTable}. A code page can be
        * inherited through LIKE option (either table- or field-level) or specified in
        * COLUMN-CODEPAGE option.
        */
       private void processCodePages()
       {
          if (master == null)
          {
             // Apply code pages inherited through LIKE option.
             //List<TableMapper.LegacyFieldInfo> fields = TableMapper.getAllLegacyFieldInfo(tempTableRef);

             DmoMeta dmoInfo = getDmoInfo();
             Iterator<Property> fields = dmoInfo.getFields(false);
             fields.forEachRemaining(field ->
             {
                if (field._fwdType == clob.class)
                {
                   String like = field.like;
                   if (!like.isEmpty())
                   {
                      int pos = like.lastIndexOf('.');
                      if (pos < 0)
                      {
                         String msg = "LIKE option " + like + " of the field " + getLegacyName() + "." +
                                      field.legacy + " must have a table qualifier";
                         ErrorManager.displayError(msg);
                         return;
                      }

                      String srcFieldName = like.substring(pos + 1);
                      String fullTableName = like.substring(0, pos);
                      Persistence persistence = DynamicTablesHelper.findExistingTable(fullTableName);
                      if (persistence == null)
                      {
                         String msg = "Could not find persistence for LIKE option " + like + " of the field " +
                                      getLegacyName() + "." + field.legacy;
                         ErrorManager.displayError(msg);
                         return;
                      }

                      if (!persistence.isTemporary())
                      {
                         // It's an open question if temp-table inherits CODEPAGE from a permanent
                         // table. 4GL behavior is buggy. I'm inclined to think that it actually
                         // inherits and this should be fixed. SVL
                         return;
                      }

                      String srcTableName = DynamicTablesHelper.parseTableName(fullTableName)[0];
                      P2JField likeField = TempTableBuilder.getExistingField(persistence,
                                                                             srcTableName,
                                                                             srcFieldName);
                      if (likeField == null)
                      {
                         String msg = "Could not find like-field for LIKE option " + like + " of the field " +
                                      getLegacyName() + "." + field.legacy;
                         ErrorManager.displayError(msg);
                         return;
                      }

                      String property = field.name;
                      String normalizedTableName4GL = TextOps.rightTrimLower(srcTableName);
                      TempTable likeTable = TempTableBuilder.getExistingTempTable(normalizedTableName4GL);
                      String likeProperty = TableMapper.getPropertyName(likeTable, likeField.getName());
                      Supplier supplier = likeTable.getCodePageSupplier(likeProperty);
                      if (supplier != null)
                      {
                         tempTableRef.setCodePageSupplier(property, supplier);
                         tempTableRef.setCodePage(property, resolveCodePage(supplier));
                      }
                      else
                      {
                         tempTableRef.setCodePage(property, likeField.getCodePage());
                      }
                   }
                }
             });
          }

          // Apply code pages specified in COLUMN-CODEPAGE option.
          if (registeredCodePages != null)
          {
             registeredCodePages.forEach((property, supplier) ->
             {
                // Shared tables use code page defined in NEW SHARED statement. But suppliers are
                // updated because they are resolved if the shared table is specified as a source in
                // a LIKE statement.
                tempTableRef.setCodePageSupplier(property, supplier);

                if (master == null)
                {
                   tempTableRef.setCodePage(property, resolveCodePage(supplier));
                }
             });

             registeredCodePages = null;
          }
       }

       /**
        * Resolves the supplier and returns the code page.
        *
        * @param  supplier
        *         Supplier to be resolved.
        *
        * @return resolved code page.
        */
       private String resolveCodePage(Supplier supplier)
       {
          Object codePage = supplier.get();
          if (codePage != null)
          {
             Class cls = codePage.getClass();
             if (String.class.isAssignableFrom(cls))
             {
                return (String) codePage;
             }
             else if (character.class.isAssignableFrom(cls))
             {
                return ((character) codePage).getValue();
             }
             else
             {
                ErrorManager.recordOrThrowError(
                      223, "Incompatible data types in expression or assignment");
             }
          }

          return null;
       }

       /**
        * Call {@link RecordBuffer#delete()} on all buffers for this temp-table, before emptying the temp-table
        * (explicitly or doing a looping delete), as any NEW record in a buffer will not be in the temporary
        * database and its reserved PK row needs to be removed explicitly, by deleting it.
        */
       private void clearBuffers()
       {
          Set<TemporaryBuffer> bufs = getAllSlaveBuffers();
          if (bufs == null)
          {
             return;
          }

          for (TemporaryBuffer buf : bufs)
          {
             if (buf.isAvailable())
             {
                BufferImpl dmo = (BufferImpl) buf.getDMOProxy();
                boolean setupBefore = dmo.isBeforeBuffer() && dmo.hasBeforeBufferConstraints();
                if (setupBefore)
                {
                   dmo.setUpBeforeBuffer(true);
                }
                try
                {
                   buf.delete();
                }
                catch (PersistenceException exc)
                {
                   ErrorManager.recordOrThrowError(exc);
                }
                finally
                {
                   if (setupBefore)
                   {
                      dmo.setUpBeforeBuffer(false);
                   }
                }
             }
          }
       }

       /**
        * Emulate a loop for deleting a set of records from this table.
        * <p>
        * This is only used when the table contains {@code Progress.Lang.Object} fields and there is
        * at least one record that holds an objects that has defined any destructors to be called when
        * it is deleted.
        * <p>
        * Because the method fetches every affected record instead of executing a bulk operation,
        * this method has a performance penalty associated.
        *
        * @param   where
        *          An FQL where clause snippet which defines the restriction criteria to apply to the
        *          delete. All references to properties in a DMO must be unqualified. {@code null} to
        *          delete all records whose multiplex IDs match that of this buffer.
        * @param   args
        *          Query substitution parameters required by the where clause. {@code null} if there
        *          are no substitutions or no where clause.
        */
       private void loopDelete(DataModelObject[] suppDMOs, String where, Object[] args)
       {
          if (where == null)
          {
             clearBuffers();
          }

          PreselectQuery query0 = new PreselectQuery()
          {
             /**
              * Check if this type of query should register record change listeners. Use this
              * to avoid computing any scope for registration and end up with a no registration
              * behavior.
              *
              * @return   {@code false} as this doesn't require any change listener.
              */
             @Override
             public boolean shouldRegisterRecordChangeListeners()
             {
                return false;
             }
          };

          boolean success = true;
          try
          {
             loopingDelete = true;

             BlockManager.forEach(query0, "innerDeleteLoop", new Block(
                (Init) () -> {
                   query0.initialize(this, where, null, null, args);
                   query0.addExternalBuffers(suppDMOs);
                },
                (Body) () -> {
                   try
                   {
                      delete();
                   }
                   catch (PersistenceException exc)
                   {
                      ErrorManager.recordOrThrowError(exc);
                   }
                }));
          }
          catch (Exception e)
          {
             success = false;
             LOG.severe("", e);
          }
          finally
          {
             loopingDelete = false;

             if (where == null)
             {
                if (success)
                {
                   local.nonEmptyTableFlags.remove(multiplexID);
                }
             }

             // if the delete was partial, there is no need to prune the session cache,
             // the delete() method handles it
             if (where == null)
             {
                pruneSessionCache();
             }
          }
       }

       /**
        * Get the order by clause determined by this buffer's backing table's primary index, if any. If no
        * explicit primary index is defined for the backing table, the effective primary index is used (the first
        * in the definition order, if any). If no index is defined, the order by clause will specify an ascending
        * sort by primary key.
        *
        * @param   tableAlias
        *          Alias used to qualify column names in the ORDER BY clause.
        *
        * @return  Order by clause for temp-table's primary index.
        */
       private String getPrimaryOrderByClause(String tableAlias)
       {
          if (primaryOrderBy != null)
          {
             return primaryOrderBy;
          }

          P2JIndex index = buffer().getDmoInfo().getPrimaryIndex(true);

          // not found? use PK as last resort
          if (index == null)
          {
             // no primary index found, use default
             if (isTemporary() && ((BufferImpl) this.getDMOProxy()).isBeforeBuffer())
             {
                primaryOrderBy = " order by _rowState asc, " + Session.PK + " asc";
             }
             else
             {
                primaryOrderBy = " order by " + Session.PK + " asc";
             }

             if (LOG.isLoggable(Level.FINER))
             {
                LOG.log(Level.FINER, "Setting default order by clause '" + primaryOrderBy + "'");
             }

             return primaryOrderBy;
          }

          // [primaryOrderBy] is null. Let's compute it based on [index] components.
          StringBuilder buf = new StringBuilder(" order by ");
          String prefix = (tableAlias != null && !tableAlias.isEmpty()) ? tableAlias + "." : "";
          ArrayList<P2JIndexComponent> comps = index.components();
          for (int i = 0; i < comps.size(); i++)
          {
             P2JIndexComponent comp = comps.get(i);
             if (i > 0)
             {
                buf.append(", ");
             }

             String name = comp.getPropertyName();
             if (comp.isCharType() && getDialect().needsComputedColumns())
             {
                name = getDialect().getComputedColumnPrefix(!comp.isIgnoreCase()) + name;
             }
             String direction = comp.isDescending() ? "desc" : "asc";

             buf.append(prefix).append(name).append(' ').append(direction);
          }
          primaryOrderBy = buf.toString();

          return primaryOrderBy;
       }

       /**
        * Open a new multiplex scope for this buffer.  The return value indicates
        * whether it is the first time a multiplex scope has been opened for this
        * buffer's DMO interface in this context.  If so, the caller will need to
        * create the backing temp table in the database.  This method permanently
        * assigns the multiplex ID to this buffer instance.
        *
        * @return  {@code true} if this is the first multiplex scope opened for the underlying DMO
        *          type; {@code false} if multiplex scopes are already open in this context for this
        *          DMO type.
        */
       private boolean openMultiplexScope()
       {
          if (!DynamicTablesHelper.isDynamicTableBuffer(this))
          {
             // buffers can be opened in an arbitrary order, so the first buffer opened for a table
             // registers static temp-table object and sets tempTableRef in the table default buffer
             if (master != null)
             {
                setTempTableRef(master.tempTableRef);
             }
          }

          Class<? extends DataModelObject> dmoIface = getDMOInterface();
          Map<Class<?>, Map<Integer, Integer>> map = local.inUseMPIDs;

          // get the map of multiplex IDs which are in use for this DMO type within our context.  The
          // value for each multiplex ID key is the reference count of buffers using that ID.
          Map<Integer, Integer> inUse = map.computeIfAbsent(dmoIface, k -> new HashMap<>());
          int refCount = 0;

          if (multiplexID == null)
          {
             // Generate a unique multiplex ID.
             Integer id = local.getNextMPID(inUse);

             // Assign the unique ID as the permanent multiplex ID of every
             // buffer in this buffer's domain.
             RecordBuffer key = getMasterBuffer();
             Map<TemporaryBuffer, Object> domain = local.domains.get(key);
             for (TemporaryBuffer next : domain.keySet())
             {
                next.multiplexID = id;
             }

             // Start off the reference count.
             refCount = 1;
          }
          else
          {
             // Increment the existing reference count for this multiplex ID.
             Integer rcObj = inUse.get(multiplexID);
             refCount = (rcObj == null ? 1 : rcObj + 1);
          }

          boolean createTable = inUse.isEmpty();

          inUse.put(multiplexID, refCount);

          // If this is the first use of this multiplex ID, the virtual table must be empty.
          if (refCount == 1)
          {
             local.nonEmptyTableFlags.remove(multiplexID);
          }

          return createTable;
       }

       /**
        * Perform a bulk delete, restricted by the given where clause and query
        * substitution parameters.
        * <p>
        * The targeted records are not actually removed from the buffer; instead,
        * they are assigned another multiplex ID, which marks them as deleted.
        * <p>
        * Note:  primary keys surrendered via bulk deletes are not currently recycled.
        *
        * @param   where
        *          An FQL where clause snippet which defines the restriction
        *          criteria to apply to the delete.  All references to properties
        *          in a DMO must be unqualified.
        * @param   args
        *          Query substitution parameters required by the where clause.
        *
        * @throws  PersistenceException
        *          if an error occurs performing the bulk delete operation.
        */
       protected void delete(String where, Object... args)
       throws PersistenceException
       {
          if (((BufferImpl) getDMOProxy()).isAfterBuffer() || forceLoopingDelete())
          {
             loopDelete(null, where, args);
          }
          else
          {
             invalidateFFCache(0);
             removeRecords(null, where, args);
          }
       }

       /**
        * Perform a bulk delete, restricted by the given where clause and query
        * substitution parameters.
        * <p>
        * The targeted records are not actually removed from the buffer; instead,
        * they are assigned another multiplex ID, which marks them as deleted.
        * <p>
        * Note:  primary keys surrendered via bulk deletes are not currently recycled.
        *
        *
        * @param   suppDMOs
        *          The DMOs for the external (additional) buffers that are accessed in inner
        *          subselect, or {@code null} in case of a simple {@code where} predicate.
        * @param   where
        *          An FQL where clause snippet which defines the restriction
        *          criteria to apply to the delete.  All references to properties
        *          in a DMO must be unqualified.
        * @param   args
        *          Query substitution parameters required by the where clause.
        *
        * @throws  PersistenceException
        *          if an error occurs performing the bulk delete operation.
        */
       protected void delete(DataModelObject[] suppDMOs, String where, Object... args)
       throws PersistenceException
       {
          if (((BufferImpl) getDMOProxy()).isAfterBuffer() || forceLoopingDelete())
          {
             loopDelete(suppDMOs, where, args);
          }
          else
          {
             removeRecords(suppDMOs, where, args);
          }
       }

       /**
        * Delete the current record from the database.  Set the current record to {@code null} if the deletion is
        * successful.  Reclaim the record's primary key for later re-use.
        *
        * @param   valexp
        *          The validation expression to evaluate.
        * @param   valmsg
        *          The error message to display when validation fails.
        *
        * @return  {@code true} if operation is successful and {@code false} otherwise.
        *
        * @throws  PersistenceException
        *          if no record currently is loaded in the buffer, or if there is an error deleting the current
        *          record from the database.
        */
       @Override
       protected boolean delete(Supplier<logical> valexp, Supplier<character> valmsg)
       throws PersistenceException
       {
          if (createRowCallback)
          {
             // deleting a transient record, see [createRowCallback] javadoc
             release(true, false);
             return true;
          }

          // check the BEFORE-BUFFER constraints
          BufferImpl buf = (BufferImpl) getDMOProxy();
          if (buf.isBeforeBuffer() && buf.hasBeforeBufferConstraints())
          {
             ErrorManager.recordOrThrowError(12374, buf.doGetName(), "");
             // You may not delete BEFORE-TABLE <name> record.  Use ACCEPT-ROW-CHANGES instead.
             return false;
          }

          if (getCurrentRecord() == null)
          {
             TempTable tempTable = TempTableBuilder.asTempTable((Temporary) getDMOProxy());
             ErrorManager.recordOrShowError(341, tempTable.name().toStringMessage());
             // ** There is no current <file-name> record. Delete failed.
             return false;
          }

          boolean autoCommit = !bufferManager.isTransaction();
          Long id = null;
          autoCommit &= persistenceContext.beginTransaction(null);

          try
          {
             if (autoCommit || loopingDelete)
             {
                id = getCurrentRecord().primaryKey();
             }

             Record b4Copy = null;
             rowid b4Rowid = null;
             Long sourceRowid = null;
             BufferImpl before = null;
             BufferImpl after = (BufferImpl) getDMOProxy();
             if (!ignoreBeforeTracking    &&
                 buffer().isAvailable()   &&
                 after.isAfterBuffer())
             {
                b4Copy = buffer().getCurrentRecord().snapshot();
                b4Rowid = after.beforeRowid();
                before = after.beforeBufferNative();
                sourceRowid = ((TempRecord) buffer().getCurrentRecord())._datasourceRowid();
             }

             super.delete(valexp, valmsg);

             if (before != null)
             {
                boolean isTrackingChanges =
                      ((AbstractTempTable) TempTableBuilder.asTempTable((Temporary) after))._isTrackingChanges();
                before.setUpBeforeBuffer(true);
                boolean updateB4RecordBuffer = true;
                if (b4Rowid != null && !b4Rowid.isUnknown())
                {
                   before.findByRowID(b4Rowid);
                   if (before._available())
                   {
                      integer rowState = before.rowState();
                      if (!rowState.isUnknown() &&
                          (!isTrackingChanges || rowState.intValue() == Buffer.ROW_CREATED))
                      {
                         before.deleteRecord();
                      }
                      updateB4RecordBuffer = false;
                   }
                }

                // if this is the buffer of an active AFTER-BUFFER, update the BEFORE-TABLE, too:
                if (b4Copy != null && isTrackingChanges)
                {
                   RecordBuffer b4Buffer = before.buffer();
                   if (updateB4RecordBuffer)  // ROW-STATE(before) == Buffer.ROW_UNMODIFIED
                   {
                      // create new before record image, then update my before-rowid and row-state
                      before.create();
                      BaseRecord.copy(b4Buffer.getCurrentRecord(), b4Copy, false);
                   }

                   if (b4Buffer.isAvailable())
                   {
                      b4Buffer.rowState(Buffer.ROW_DELETED);
                      b4Buffer.peerRowid(new rowid());
                      if (sourceRowid != null)
                      {
                         ((TempRecord) b4Buffer.getCurrentRecord())._datasourceRowid(sourceRowid);
                      }
                   }

                   // do not release the before buffer, let it hold the newly created row, but flush it!
                   if (b4Buffer.isAvailable())
                   {
                      try
                      {
                         b4Buffer.flush();
                      }
                      catch (ValidationException e)
                      {
                         // this should not happen, there are no constraints to validate on before records
                         throw new PersistenceException(e);
                      }
                   }
                }
                before.setUpBeforeBuffer(false);
             }

             if (autoCommit || loopingDelete)
             {
                local.reclaimKey(getDMOInterface(), getMultiplexID(), id);
             }
          }
          finally
          {
             if (autoCommit)
             {
                try
                {
                   persistenceContext.commit();
                }
                catch (PersistenceException exc)
                {
                   // TODO: what to do here?
                   if (LOG.isLoggable(Level.WARNING))
                   {
                      LOG.log(Level.WARNING, "Error committing delete", exc);
                   }
                }
             }
          }

          // everything went fine
          return true;
       }

       /**
        * Reclaim the given list of primary keys for re-use.
        *
        * @param   keys
        *          A list of primary keys which are available for re-use.  Must
        *          not be <code>null</code>.
        */
       @Deprecated
       @Override
       protected void reclaimKeys(List<Long> keys)
       {
          Class<? extends DataModelObject> dmoIface = getDMOInterface();
          Integer multiplexID = getMultiplexID();
          for (Long key : keys)
          {
             local.reclaimKey(dmoIface, multiplexID, key);
          }
       }

       /**
        * Returns the primary key for a new DMO associated with this buffer.
        *
        * @return  The primary key for a new DMO associated with this buffer.
        */
       @Override
       protected Long nextPrimaryKey()
       {
          return local.nextPrimaryKey(getDMOInterface(), getMultiplexID());
       }

       /**
        * Check if this temp-table is currently being queried.
        *
        * @return   See above.
        *
        * @see  ChangeBroker#isQueried
        */
       protected boolean isQueried()
       {
          return changeBroker.isQueried(getDMOInterface(), getMultiplexID());
       }

       /**
        * Check if the delete operation must be forced using a loop, and not a batch sql.
        *
        * @return  {@code true} if the buffer is currently being queried or any object fields have a destructor.
        */
       protected boolean forceLoopingDelete()
       {
          return isQueried() || hasOoDestructors() || getPersistenceContext().getNursery().containsRecordBufferDmo(this);
       }

       /**
        * Close the current multiplex scope for the underlying DMO type in the current context. Remove all records
        * associated with this buffer's multiplex ID and adjust the multiplex ID in use map. If the multiplex is
        * not in use anymore, the legacy information is removed from {@code TableMapper}. If this is the last
        * scope to be closed for this DMO type, also request the context to drop the backing temp table from the
        * database when the database session is closed.
        *
        * @throws  PersistenceException
        *          if any error occurs within a listener while processing a change event.
        */
       private void doCloseMultiplexScope()
       throws PersistenceException
       {
          // do we need to copy our current records before cleaning up?
          if (outputParameters != null)
          {
             // use a copy, as outputParameters gets cleaned up by performOutputCopy
             List<TableParameter> copy = new LinkedList<>(outputParameters);
             for (TableParameter outputParam : copy)
             {
                performOutputCopy(outputParam);
             }
          }

          // invalidate the cache for this DMO and its multiplex ID, as it is going out of scope/deleted.
          persistenceContext.getFastFindCache().invalidate(getDmoInfo().getId(), multiplexID, true);

          Class<? extends DataModelObject> dmoIface = getDMOInterface();
          Map<Class<?>, Map<Integer, Integer>> map = local.inUseMPIDs;

          // get the map of multiplex IDs which are in use for this DMO type within our context
          Map<Integer, Integer> inUse = map.get(dmoIface);

          // access our reference count from this map
          int refCount = inUse.get(multiplexID);

          // decrement the reference count.  If 0, remove the key, else store the new value back in the map
          if (--refCount == 0)
          {
             inUse.remove(multiplexID);
          }
          else
          {
             inUse.put(multiplexID, refCount);
          }

          // if the in-use map is now empty, the backing table will need to be dropped.  Also remove
          // the inUse map from the master map and remove this buffer's domain if it is the master buffer
          if (inUse.isEmpty())
          {
             map.remove(dmoIface);

             try
             {
                removeRecords(null, null, null);
             }
             catch (PersistenceException exc)
             {
                ErrorManager.recordOrThrowError(exc);
             }
             finally
             {
                local.nonEmptyTableFlags.remove(multiplexID);

                // prepare the table to be dropped when the database session closes
                local.dropTable(tempTableResource(), getTable(), dmoIface);

                pruneSessionCache();

                DirectAccessHelper.killMultiplex(local.pctx.getSession(), getDmoInfo().getSqlTableName(), multiplexID);
             }
          }
          // otherwise, if the current multiplexID is no longer in use, remove all records for this multiplex ID
          // from the table, but leave the table intact for the other buffers still using it.
          else if (refCount == 0)
          {
             try
             {
                removeRecords(null, null, null);
             }
             catch (PersistenceException exc)
             {
                ErrorManager.recordOrThrowError(exc);
             }
             finally
             {
                local.nonEmptyTableFlags.remove(multiplexID);

                pruneSessionCache();

                DirectAccessHelper.killMultiplex(local.pctx.getSession(), getDmoInfo().getSqlTableName(), multiplexID);
             }
          }
       }

       /**
        * Determine if the multiplex scope for the underlying DMO type in the current context is
        * closed. See {@link #closeMultiplexScope()} for more information.
        *
        * @return <code>true</code> if multiplex scope is closed.
        */
       private boolean isMultiplexScopeClosed()
       {
          Map<Class<?>, Map<Integer, Integer>> map = local.inUseMPIDs;

          // Get the map of multiplex IDs which are in use for this DMO type within our context.
          Map<Integer, Integer> inUse = map.get(getDMOInterface());

          if (inUse == null)
          {
             return true;
          }

          // Access our reference count from this map.
          Integer rc = inUse.get(multiplexID);

          return rc == null;
       }

       /*
       private void count()
       {
          Persistence persistence = getPersistence();
          StringBuilder buf = new StringBuilder("select count(*) from ");
          buf.append(getDMOImplementationName());
          String fql = buf.toString();
          try
          {
             List list = persistence.list(fql, null, null, 0, 0, true);
             Number count = (Number) list.get(0);
             LOG.info(getTable() + " contains " + count + " records.");
          }
          catch (PersistenceException exc)
          {
             ErrorManager.recordOrThrowError(-1,
                                             "Error counting temp table records",
                                             exc);
          }
       }
       */

       /**
        * Remove or change the multiplex ID for all record rows from the backing table which meet the
        * specified criteria. Because temp table instances are multiplexed across nested buffer scopes
        * and across multiple buffers which use the same backing DMO type, the backing table cannot be
        * dropped until after the last buffer scope referencing the backing table in the current
        * context has been closed.  This method is invoked for all buffer scope closing events.
        * This is also the worker method for the {@link #deleteAll()} and
        * {@link RecordBuffer#delete(DataModelObject[], String, Object[])} methods.
        *
        * @param   extDMOs
        *          The DMOs for the external (additional) buffers that are accessed in inner
        *          subselect, or {@code null} in case of a simple {@code where} predicate.
        * @param   where
        *          An FQL where clause snippet which defines the restriction
        *          criteria to apply to the delete.  All references to properties
        *          in a DMO must be unqualified.  <code>null</code> to delete all
        *          records whose multiplex IDs match that of this buffer.
        * @param   args
        *          Query substitution parameters required by the where clause.
        *          <code>null</code> if there are no substitutions or no where clause.
        *
        * @throws  PersistenceException
        *          if an error occurs performing the bulk delete operation.
        */
       private void removeRecords(DataModelObject[] extDMOs, String where, Object[] args)
       throws PersistenceException
       {
          if (where == null)
          {
             clearBuffers();
          }

          Record currentRec = getCurrentRecord();

          // after executing this method, the buffer is empty regardless the where predicate matches
          // the currently stored record
          // TODO: analyze whether it is appropriate to allow write trigger to fire here; this
          //       method can be invoked in various situations
          release(true);

          if (isTableDefinitelyEmpty())
          {
             // nothing to do
             return;
          }

          // enforce the savepoint, in case of rollback, but only if we are not closing the multiplex
          if (!isMultiplexScopeClosed() && undoStateProvider != null && undoStateProvider.isUndoable())
          {
             getSession(true).lazilySetSavepoints();
          }

          FastFindCache ffCache = persistenceContext.getFastFindCache();
          if (extDMOs == null || extDMOs.length == 0)
          {
             ffCache.invalidate(getDmoInfo().getId(), multiplexID);
          }
          else
          {
             int len = extDMOs.length;
             List<Long> tableIds = new ArrayList<>(len + 1);
             tableIds.add(FastFindCache.combine(getDmoInfo().getId(), multiplexID));
             for (int i = 0; i < len; i++)
             {
                RecordBuffer buffer = ((BufferReference) extDMOs[i]).buffer();
                int id = buffer.getDmoInfo().getId();
                tableIds.add(FastFindCache.combine(id, buffer.getMultiplexID()));
             }
             ffCache.invalidate(tableIds);
          }

          if (args != null &&
              !AbstractQuery.preprocessSubstitutionArguments(bufferManager, () -> false, false, args))
          {
             // there was an error processing where clause arguments; abort
             return;
          }

          boolean hasWhere = (where != null);
          Persistence persistence = getPersistence();

          // native SQL is more efficient, so we use it in the simple case of deleting/updating
          // everything
          boolean useSql = !hasWhere && (args == null || args.length == 0);

          String dmoImplName = getDMOImplementationName();
          String dmoAlias = getDMOAlias();
          String aliasDot = dmoAlias + ".";

          StringBuilder buf = new StringBuilder();

          boolean hasOO = !getDmoInfo().getOoFields().isEmpty();
          StringBuilder fql = new StringBuilder();
          if (hasOO)
          {
             fql.append("from ").append(dmoImplName).append(" as ").append(dmoAlias);
             fql.append(" where ");
          }

          List<Object> additionalArgs = new ArrayList<>(2);
          List<Object> ooArgs = new ArrayList<>(2);
          // create portion of where clause which restricts by multiplex ID
          buf.append(aliasDot);
          buf.append(MULTIPLEX_FIELD_NAME);
          buf.append(" = ?");
          String mpidWhere = buf.toString();

          FQLExpression query = new FQLExpression();

          query.append("delete from ");

          String table = getTable();
          if (useSql)
          {
             query.append(table);
          }
          else
          {
             query.append(dmoImplName);
          }
          query.append(" as ").append(dmoAlias);
          query.append(" where ");

          // apply multiplex filter and any additional conditions
          if (hasWhere)
          {
             query.append("(");
             if (hasOO) { fql.append("("); }
          }
          query.append(mpidWhere);
          additionalArgs.add(multiplexID);
          if (hasOO)
          {
             fql.append(dmoAlias).append(".").append(MULTIPLEX_FIELD_NAME).append("=?");
             ooArgs.add(multiplexID);
          }
          if (hasWhere)
          {
             query.append(") and (");
             if (hasOO) { fql.append(") and ("); }

             // add the external buffers to the list
             RecordBuffer[] buffs = new RecordBuffer[extDMOs == null ? 1 : extDMOs.length + 1];
             buffs[0] = this;
             if (extDMOs != null)
             {
                for (int i = 0; i < extDMOs.length; i++)
                {
                   buffs[i + 1] = ((BufferReference) extDMOs[i]).buffer();
                }
             }
             FQLPreprocessor preproc = FQLPreprocessor.get(where,
                                                           getDatabase(),
                                                           getDialect(),
                                                           buffs,
                                                           args,
                                                           null,
                                                           false,
                                                           null,
                                                           null,
                                                           false,
                                                           null);
             // we need to use the preprocessed where clause
             where = preproc.getFQL().toFinalExpression();
             query.append(preproc.getFQL());
             query.append(")");
             if (hasOO)
             {
                fql.append(preproc.getFQL());
                fql.append(")");
             }

             if (args != null && args.length > 0)
             {
                ParameterIndices pi = preproc.getParameterIndices();
                int len = pi.getCount();
                List<Object> argList = new ArrayList<>(len);
                for (int i = 0, count = 0; count < len; i++)
                {
                   Integer nextIndex = pi.getIndex(i);
                   if (nextIndex == null)
                   {
                      // original substitution parameter was inlined into FQL by preprocessor; skip it.
                      continue;
                   }

                   // substitution placeholders may have been reordered if this statement was
                   // rewritten by the FQL preprocessor; make sure we access the correct
                   // substitution parameter
                   Object next = args[nextIndex];
                   argList.add(next);

                   count++;
                }
                args = argList.toArray();
             }
          }
          String delStmt = query.toFinalExpression();

          boolean full = !hasWhere;

          // before performing the actual operation, retrieve all records about to be deleted and
          // decrement the ref-counter for Object fields.
          if (hasOO)
          {
             if (args != null)
             {
                Collections.addAll(ooArgs, args);
             }

             List<Record> recs = persistence.list(null, fql.toString(), ooArgs.toArray(), 0, 0, true);
             if (recs != null)
             {
                Map<String, Integer> extentMap = getDmoInfo().getExtentMap();
                Map<String, Method> getterMap = getDmoInfo().getPojoGetterMap();
                for (Record dmo : recs)
                {
                   for (String field : getDmoInfo().getOoFields())
                   {
                      Method method = getterMap.get(field);
                      PropertyMeta meta = getDmoInfo().getPropsByGetterMap().get(method);
                      DataHandler handler = meta.getDataHandler();
                      int offset = meta.getOffset();
                      Integer extent = extentMap.get(field);
                      if (extent == null)
                      {
                         object val = (object) handler.getField(dmo, offset);
                         decrementObjectCountRef(val);
                      }
                      else
                      {
                         for (int k = 0; k < extent; k++)
                         {
                            object val = (object) handler.getField(dmo, offset + k);
                            decrementObjectCountRef(val);
                         }
                      }
                   }
                }
             }
          }

          // the index-based cache was already invalidated at the top of this method
          int rowCount = 0;
          if (useSql)
          {
             // sql is used only in full mode
             rowCount = persistence.executeSQL(delStmt,
                                               Persistence.TEMP_CTX,
                                               additionalArgs.toArray(),
                                               !isUndoable());
          }
          else
          {
             if (args != null)
             {
                Collections.addAll(additionalArgs, args);
             }

             Session session = persistenceContext.getSession();
             if (!full)
             {
                // evict from cache all records which will be deleted
                String pkSelect = "select " + dmoAlias + "." + Session.PK + " " +
                                  delStmt.substring("delete ".length());
                ScrollableResults<Long> res = persistence.scroll(pkSelect, additionalArgs.toArray());
                while (res.next())
                {
                   Object[] o = res.get();
                   Long pk = (Long) o[0];
                   BaseRecord rec = session.getCached(getDMOImplementationClass(), pk);
                   if (rec != null)
                   {
                      session.evict(rec);
                      rec.updateState(session, DmoState.DELETED, true);
                   }
                }
                res.close();
             }

             rowCount = persistence.deleteOrUpdate(delStmt,
                                                   Persistence.TEMP_CTX,
                                                   additionalArgs.toArray(),
                                                   getEntityName(),
                                                   !isUndoable());
          }

          if (rowCount > 0)
          {
             if (!full)
             {
                Boolean hasRecords = this.fastHasRecords();
                if (hasRecords != null)
                {
                   full = !hasRecords;
                }
                else
                {
                   Object[] multiplexArgs = new Object[] {multiplexID};
                   // execute a simple query to determine if there is any record in this virtual table
                   String checkStmt =
                         "select tt." + Session.PK + " from " + table + " tt " +
                         "where tt." + MULTIPLEX_FIELD_NAME + " = ?" + " limit 1";
                   Long id = persistence.getSingleSQLResult(checkStmt, Persistence.TEMP_CTX, multiplexArgs);

                   full = (id == null);
                }
             }

             // Notify all interested parties that a bulk delete occurred.
             // Listening buffers may need to release deleted records.
             RecordChangeEvent.Type eventType =
                (full
                 ? RecordChangeEvent.Type.FULL_BULK_DELETE
                 : RecordChangeEvent.Type.RESTRICTED_BULK_DELETE);
             changeBroker.stateChanged(eventType, this, full ? currentRec : null, null, null);
          }

          // if the virtual table is now empty, mark it as such
          if (full)
          {
             local.nonEmptyTableFlags.remove(multiplexID);
          }

          // WARNING: session cache can't be pruned unless all records are being deleted from the table
          if (full)
          {
             pruneSessionCache();
          }
       }

       /**
        * Get the {@link TempTable} resource associated with this temporary buffer.
        *
        * @return   See above.
        */
       private TempTable tempTableResource()
       {
          return tempTableRef;
       }

       /**
        * Functional interface for setting a BEFORE-BUFFER field.
        */
       @FunctionalInterface
       private interface BeforeFieldUpdater
       {
          /**
           * Actual update of the field.
           *
           * @param   record
           *          The {@code TempRecord} to be updated.
           */
          void update(TempRecord record);
       }

       /**
        * Common implementation for all setters of BEFORE-BUFFER special (hidden) fields.
        *
        * @param   updater
        *          The (lambda) code that does the actual field update.
        * @param   changedField
        *          The java name of the field to be updated. used to notify the {@code ChangeBroker}.
        * @param   legacy
        *          The legacy name of the field. Only used for logging when operation fails.
        * @param   updatePeer
        *          Update the peer too: make the same change to the peer record.
        */
       private void setupBeforeFlagImpl(BeforeFieldUpdater updater,
                                        String changedField,
                                        String legacy,
                                        boolean updatePeer)
       {
          if (!isAvailable())
          {
             return;
          }

          TempRecord record = getCurrentRecord();
          TempRecord snapshot = (TempRecord) record.deepCopy();
          updater.update(record);
          record.updateState(null, DmoState.CHANGED, true);

          try
          {
             Map<String, List<Integer>> props = new HashMap<>();
             props.put(changedField, null);
             changeBroker.stateChanged(RecordChangeEvent.Type.UPDATE, this, record, snapshot, props);
             Long peerRowid = record._peerRowid();

             if (updatePeer && peerRowid != null)
             {
                RecordBuffer peerBuffer = ((BufferImpl) getDMOProxy()).peerBuffer.buffer();
                Class<? extends Record> peerClass = peerBuffer.getDMOImplementationClass();
                Session session = getPersistence().getSession(Persistence.TEMP_CTX);
                TempRecord peerRecord = (TempRecord) session.get(peerClass, peerRowid);
                if (peerRecord == null)
                {
                   // the peer record was created but not reached the session's cache yet, check the peer buffer
                   if (peerRowid.equals(peerBuffer.rowID().getValue()))
                   {
                      peerRecord = (TempRecord) peerBuffer.getCurrentRecord();
                   }
                   else
                   {
                      // where is the peer record?
                      throw new RuntimeException("Failed to locate record #" + Long.toHexString(peerRowid));
                   }
                }

                // check if the peer record was changed since it was saved or fetched from database
                boolean doSave = !peerRecord.checkState(DmoState.NEW) &&
                                 !peerRecord.checkState(DmoState.CHANGED);

                TempRecord peerSnapshot = (TempRecord) peerRecord.deepCopy();
                updater.update(peerRecord);
                peerRecord.updateState(null, DmoState.CHANGED, true);

                // reusing props
                changeBroker.stateChanged(RecordChangeEvent.Type.UPDATE,
                                          peerBuffer,
                                          peerRecord,
                                          peerSnapshot,
                                          props);

                if (doSave)
                {
                   // evidently, the record was changed locally, must flush it to bring it back to same state
                   Savepoint sp = session.setSavepoint();
                   boolean fullTx = false;

                   if (sp == null)
                   {
                      // not currently within a transaction? Create a micro-transaction to persist the change
                      fullTx = session.beginTransaction(null);
                   }

                   try
                   {
                      session.save(peerRecord, true);
                   }
                   catch (PersistenceException exc)
                   {
                      // did something go wrong?
                      if (sp != null)
                      {
                         session.rollbackSavepoint(sp);
                      }
                      else if (fullTx)
                      {
                         session.rollback();
                      }
                   }

                   // release the temporary savepoint or commit the micro-transaction
                   if (sp != null)
                   {
                      session.releaseSavepoint(sp);
                   }
                   else if (fullTx)
                   {
                      session.commit();
                   }
                }
             }
          }
          catch (PersistenceException pe)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING, "Failed to set " + legacy, pe);
             }
          }
       }

       /**
        * Helper which tracks the multiplex IDs currently in use by DMO interface
        * in the current client context, as well as the reference count of temp
        * tables created in the database using the context's current connection,
        * and domains of master/slave buffers.
        * <p>
        * This object also tracks tables which have been created during the
        * current database session, and drops them when the session is being
        * closed.  This is done as an optimization to prevent potential thrashing
        *in the event a class which creates a temporary buffer and then allows it
        * to go out of scope is invoked repeatedly in a loop.
        */
       private static class Context
       implements SessionListener
       {
          /** Map of DMO interfaces to counts of multiplex IDs currently in use */
          private final Map<Class<?>, Map<Integer, Integer>> inUseMPIDs = new IdentityHashMap<>();

          /** Map of open tables to most recent primary key ID generated */
          private final Map<Class<? extends DataModelObject>, AtomicLong> openTables = new IdentityHashMap<>();

          /** Set of DMO interfaces whose corresponding tables will be dropped at the next commit */
          private final Set<Class<? extends DataModelObject>> pendingDrop =
                                                               Collections.newSetFromMap(new IdentityHashMap<>());

          /** Names of active temp tables in the order they were created */
          private final Set<String> activeTables = new LinkedHashSet<>();

          /** Map of flags indicating whether each virtual table is empty */
          private final RoaringBitmap nonEmptyTableFlags = new RoaringBitmap();

          /** Singleton map of a unique key to session ID string */
          private final Map<String, String> sweepMap;

          /** Persistence service object for temp tables */
          private Persistence persistence;

          /** Persistence context for transaction management */
          private Persistence.Context pctx;

          /** Map of master buffers to weak hash maps with slave buffer keys */
          private Map<TemporaryBuffer, Map<TemporaryBuffer, Object>> domains = new WeakHashMap<>();

          /**
           * The counter of stored {@code object}s for each DMO interface.
           * <p>
           * Only the {@code object}s that have active destructors are counted.
           */
          private final Map<Class<?>, RefCount> ooObjectCounter = new IdentityHashMap<>();

          /** Next value to try when assigning a multiplex ID */
          private int nextMPID = 1;

          /** Cache responsible with storing instances of FastCopyHelper. */
          private final Cache<FastCopyKey,
                              FastCopyHelper> fastCopyCache = CacheManager.createLRUCache(TemporaryBuffer.class,
                                                                                          256);

          /** Flag to indicate that this context database session has the mapping tables for fast-copy created */
          private boolean hasMappingTables = false;

          /**
           * Default constructor.
           */
          Context()
          {
             SecurityManager secMgr = SecurityManager.getInstance();
             String sid = String.valueOf(secMgr.sessionSm.getSessionId());
             sweepMap = Collections.singletonMap(TempTableHelper.UNIQUE_SUFFIX_KEY, sid);
          }

          /**
           * This method is invoked by the persistence context when a Hibernate session has just
           * ended a transaction or is about to close. Used as a hook to cleanup temp-table and
           * other resources.
           *
           * @param   event
           *          Session event type.
           *
           * @return  {@code false}, indicating that this session listener should remain registered
           *          after this method returns.
           *
           * @throws  PersistenceException
           *          if any error occurs dropping temp tables from the closing session.
           */
          public boolean sessionEvent(SessionListener.Event event)
          throws PersistenceException
          {
             switch (event)
             {
                case SESSION_CLEANUP:
                   dropUnusedTables(false);

                   break;

                case SESSION_CLOSING_NORMALLY:
                   dropUnusedTables(true);
                   cleanup();

                   break;

                case SESSION_CLOSING_WITH_ERROR:
                   // On error, we want TempTableConnectionProvider to release the
                   // broken connection, so make sure we are not pinning it open.
                   activeTables.clear();
                   pendingDrop.clear();

                   // Clear remainder of state.
                   Iterator<Class<? extends DataModelObject>> iter = openTables.keySet().iterator();
                   while (iter.hasNext())
                   {
                      Class<?> next = iter.next();
                      inUseMPIDs.remove(next);
                      iter.remove();
                   }

                   cleanup();

                   break;

                default:

                   break;
             }

             return false;
          }

          /**
           * Invoked when this session listener is removed from the list of
           * registered session listeners.
           * <p>
           * This implementation does nothing.
           */
          public void deregisteredSessionListener()
          {
          }

          /**
           * Initialize this object by storing the persistence service object and
           * registering as a session listener.  This method is tolerant of being
           * invoked multiple times, but only does its initialization work the
           * first time it is invoked for the current <code>Context</code>
           * instance.
           */
          void init()
          {
             if (persistence != null)
             {
                return;
             }

             persistence = PersistenceFactory.getInstance(DatabaseManager.TEMP_TABLE_DB);
             persistence.registerSessionListener(this, SessionListener.Scope.NONE, Persistence.TEMP_CTX);
             pctx = persistence.getContext(Persistence.TEMP_CTX);
          }

          /**
           * Destroy the user's in-DB PK mapping tables.
           */
          void cleanup()
          {
             if (hasMappingTables)
             {
                try (Session session = new Session(persistence.getDatabase(Persistence.TEMP_CTX)))
                {
                   FastCopyHelper.dropMappings(session);
                   hasMappingTables = false;
                }
                catch (PersistenceException e)
                {
                   if (LOG.isLoggable(Level.SEVERE))
                   {
                      LOG.log(Level.SEVERE, "Problem dropping user's mapping tables.", e);
                   }
                }
             }
          }

          /**
           * Report the number of temp tables which currently are in use in the
           * current context for the database associated with this buffer.
           *
           * @return  Number of temp tables associated with
           *          <code>persistence</code>.
           */
          int getTableCount()
          {
             return activeTables.size();
          }

          /**
           * Returns a context-local multiplex ID value.
           *
           * @param   inUse
           *          Map of multiplex IDs currently in use in this context, to
           *          reference count of buffers using them.
           *
           * @return  The next available multiplex ID.
           */
          Integer getNextMPID(Map<Integer, Integer> inUse)
          {
             Integer id = null;
             do
             {
                id = nextMPID++;
             }
             while (inUse.containsKey(id));

             return id;
          }

          /**
           * Create a temp table in the database for the given DMO interface, if it does not already
           * exist. This will also create all indexes defined for the temp table.
           *
           * @param   dmoIface
           *          DMO interface for which a backing temp table is needed.
           * @param   table
           *          Name of temp table to be created.
           */
          void createTable(Class<? extends DataModelObject> dmoIface, String table)
          {
             init();

             activeTables.add(table);

             // remove from pending drop tables, if present
             boolean notPendingDrop = !pendingDrop.remove(dmoIface);

             // add to open tables, if not already present
             boolean notOpen = (openTables.putIfAbsent(dmoIface, new AtomicLong()) == null);

             // create table only if it doesn't already exist (don't want overhead of using IF NOT
             // EXISTS at the database)
             if (notPendingDrop && notOpen)
             {
                doCreateTable(dmoIface);
             }
          }

          /**
           * Remove the given table from the active temp tables in use within the current context,
           * and add it to the pending drop tables. This method does not actually drop a table,
           * since all table drops are deferred until the database session notifies it needs cleanup.
           *  This prevents potential thrashing in the event a class which creates a temporary buffer
           * and then allows it to go out of scope is invoked repeatedly in a loop.
           *
           * @param   tt
           *          The temp-table resource.
           * @param   table
           *          Name of temp table to be dropped.
           * @param   dmoIface
           *          DMO interface.
           */
          void dropTable(TempTable tt, String table, Class<? extends DataModelObject> dmoIface)
          {
             activeTables.remove(table);

             pendingDrop.add(dmoIface);
          }

          /**
           * Returns the next available primary key for the temp table associated with the given DMO interface.
           *
           * @param   dmoIface
           *          Data model object interface.
           * @param   multiplexID
           *          The multiplex ID for the temp-table reclaiming this key.
           *
           * @return  The primary key for a new DMO associated with this buffer.
           */
          Long nextPrimaryKey(Class<? extends DataModelObject> dmoIface, Integer multiplexID)
          {
              try
              {
                 DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
                 {
                    //  let FWD-H2 do the primary key providing
                    return DirectAccessHelper.nextPrimaryKey(persistence.getSession(!dmoInfo.multiTenant),
                                                             dmoInfo.getSqlTableName(),
                                                             multiplexID);
                 }
              }
              catch (DirectAccessException e)
              {
                 // fall-back to the old fashioned in memory sequence
                 // this is not optimal as it doesn't reclaim unused keys
                 if (LOG.isLoggable(Level.SEVERE) && !e.isExpected())
                 {
                    LOG.log(Level.SEVERE, "Direct access failure when obtaining the next key: " + e.getMessage(), e);
                 }

                 AtomicLong id = openTables.get(dmoIface);
                 return id.incrementAndGet();
              }
          }

          /**
           * Prepare a single, primary key for reclamation.  On the next, full
           * transaction commit, this key will be made available for re-use.
           * Until then, it will be stored in a set of keys which are pending
           * reclamation.  This set will be cleared if the current transaction is
           * rolled back.
           *
           * @param   dmoIface
           *          DMO interface for temporary table records.
           * @param   multiplexID
           *          The multiplex ID for the temp-table reclaiming this key.
           * @param   id
           *          Primary key to be recycled.
           */
          @Deprecated
          void reclaimKey(Class<?> dmoIface, Integer multiplexID, Long id)
          {

          }

          /**
           * Drop all temporary tables which have been created during the current database session,
           * but which are no longer in use. This occurs when the session notifies that it needs
           * cleanup.
           *
           * @param   sessionClosing
           *          {@code true} to mark all open tables as unused, so they are dropped;
           *          {@code false} to only drop tables previously marked as ready to be dropped.
           *
           * @throws  PersistenceException
           *          if a database error occurs dropping any table or creating
           *          the transaction which contains these actions.
           */
          private void dropUnusedTables(boolean sessionClosing)
          throws PersistenceException
          {
             if (sessionClosing)
             {
                pendingDrop.addAll(openTables.keySet());
             }

             if (pendingDrop.isEmpty())
             {
                return;
             }

             boolean commit = pctx.beginTransaction(null);

             try
             {
                Iterator<Class<? extends DataModelObject>> iter = pendingDrop.iterator();
                while (iter.hasNext())
                {
                   Class<? extends DataModelObject> dmoIface = iter.next();
                   iter.remove();
                   openTables.remove(dmoIface);
                   doDropTable(dmoIface);
                }

                if (commit)
                {
                   pctx.commit();
                }
             }
             finally
             {
                if (sessionClosing)
                {
                   openTables.clear();
                   pendingDrop.clear();
                }
             }
          }

          /**
           * Get the implicit SQL index name.
           *
           * @param   dmoIface
           *          DMO interface which represents the temp table.
           *
           * @return  See above.
           */
          private String getImplicitSqlIndexName(Class<? extends DataModelObject> dmoIface)
          {
             String idxName = TempTableHelper.getImplicitSqlIndexName(
                   persistence.getDatabase(Persistence.TEMP_CTX), dmoIface);
             return StringHelper.sweep(idxName, sweepMap);
          }

          /**
           * Get the implicit index name.
           *
           * @param   dmoIface
           *          DMO interface which represents the temp table.
           *
           * @return  See above.
           */
          private String getImplicitIndexName(Class<? extends DataModelObject> dmoIface)
          {
             String idxName = TempTableHelper.getImplicitIndexName(
                   persistence.getDatabase(Persistence.TEMP_CTX), dmoIface);
             return StringHelper.sweep(idxName, sweepMap);
          }

          /**
           * Issue the SQL statement(s) against the backing database necessary to
           * create the backing temp table needed for the given DMO interface, as
           * well as all indexes defined for that table.  Because temp table
           * instances are multiplexed across nested buffer scopes and across
           * multiple buffers which use the same backing DMO type, this method is
           * invoked only the first time a backing table is actually needed for a
           * particular DMO interface type in the current context.  Thereafter,
           * this method is not invoked again, unless the backing table has first
           * been dropped.
           *
           * @param   dmoIface
           *          DMO interface for which a backing temp table is needed.
           *
           * @throws  ErrorConditionException
           *          if an error occurs creating the table and silent error mode
           *          is disabled.
           *
           * @see     #doDropTable
           */
          private void doCreateTable(Class<? extends DataModelObject> dmoIface)
          {
             String sql = null;

             try
             {
                if (LOG.isLoggable(Level.FINER))
                {
                   String msg = Utils.describeContext() + "Creating temp-table for " + dmoIface;
                   LOG.log(Level.FINER, msg, new Throwable());
                }

                Database database = persistence.getDatabase(Persistence.TEMP_CTX);
                List<String> sqlList = new ArrayList<>();

                // Create table(s).
                Iterator<String> tableIter = TempTableHelper.sqlTempTableCreate(database, dmoIface);
                while (tableIter.hasNext())
                {
                   sql = tableIter.next();
                   sqlList.add(sql);
                }

                // Create indexes (if any).
                Iterator<String> indexIter = TempTableHelper.sqlTempTableIndexCreate(database, dmoIface);
                while (indexIter.hasNext())
                {
                   sql = indexIter.next();
                   sql = StringHelper.sweep(sql, sweepMap);
                   sqlList.add(sql);
                }

                persistence.executeSQLBatch(sqlList, true, Persistence.TEMP_CTX);

                pctx.useSession();
             }
             catch (PersistenceException exc)
             {
                openTables.remove(dmoIface);

                ErrorManager.recordOrThrowError(
                   -1,
                   "Error creating temp table:  " + getTableName(dmoIface) + " [" + sql + "]",
                   exc);
             }
          }

          /**
           * Issue the SQL statement(s) against the backing database necessary to
           * drop the backing temp table for the given DMO interface.  Because
           * temp table instances are multiplexed across nested buffer scopes and
           * across multiple buffers which use the same backing DMO type, this
           * method is invoked only when the current database session is being
           * closed, after the last buffer scope referencing the backing table in
           * the current context has been closed.
           *
           * @param   dmoIface
           *          DMO interface for which the backing temp table should be
           *          dropped.
           *
           * @throws  PersistenceException
           *          if an error occurs dropping the table and silent error mode
           *          is disabled.
           *
           * @see     #doCreateTable
           */
          private void doDropTable(Class<? extends DataModelObject> dmoIface)
          throws PersistenceException
          {
             try
             {
                if (LOG.isLoggable(Level.FINER))
                {
                   String msg = Utils.describeContext() + "Dropping temp-table for " + dmoIface;
                   LOG.log(Level.FINER, msg, new Throwable());
                }

                pctx.releaseSession();

                List<String> sqlList = new ArrayList<>();

                Database database = persistence.getDatabase(Persistence.TEMP_CTX);

                // drop indexes (if any)
                Iterator<String> indexIter = TempTableHelper.sqlTempTableIndexDrop(database, dmoIface);
                while (indexIter.hasNext())
                {
                   String sql = indexIter.next();
                   sql = StringHelper.sweep(sql, sweepMap);
                   sqlList.add(sql);
                }

                // drop table
                Iterator<String> tableIter = TempTableHelper.sqlTempTableDrop(database, dmoIface);
                while (tableIter.hasNext())
                {
                   String sql = tableIter.next();
                   sqlList.add(sql);
                }

                persistence.executeSQLBatch(pctx, sqlList, true);
             }
             catch (PersistenceException exc)
             {
                throw new PersistenceException( "Error dropping temp table:  " + getTableName(dmoIface), exc);
             }
          }

          /**
           * Get the backing temp table name associated with the given DMO
           * interface, primarily for debugging and error reporting purposes.
           *
           * @param   dmoIface
           *          DMO interface for which a table name is needed.
           *
           * @return  Name of the temp table associated with the given interface.
           */
          private String getTableName(Class<?> dmoIface)
          {
             DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
             return dmoInfo.getSqlTableName();
          }

          /**
           * Get an instance of a {@code FastCopyHelper} from the cache if it exists, or create a new one
           * if there is no suitable instance in the cache.
           *
           * @param   fastCopyContext
           *          The context for the {@code FastCopyHelper}.
           *
           * @return  FastCopyHelper
           *          Instance of {@code FastCopyHelper}, either retrieved from cache, or just created.
           */
          private FastCopyHelper getFastCopyHelper(FastCopyContext fastCopyContext)
          {
             if (!hasMappingTables)
             {
                hasMappingTables = true;
                FastCopyHelper.createMappings(fastCopyContext.srcBuf.getPersistence());
             }

             FastCopyKey key = new FastCopyKey(fastCopyContext);
             FastCopyHelper fastCopyHelper = fastCopyCache.get(key);
             if (fastCopyHelper == null)
             {
                fastCopyHelper = fastCopyContext.build();
                fastCopyCache.put(key, fastCopyHelper);
             }

             return fastCopyHelper;
          }
       }

       /**
        * An object registered with the <code>TransactionManager</code> to be
        * notified when its enclosing buffer's scope is closing.  This drives
        * cleanup of the backing temp table, which can take one of two forms:
        * <ul>
        *   <li>if no other buffers are using the backing table any longer, the
        *       table is dropped (along with all records);
        *   <li>otherwise, only those records associated with this buffer
        *       instance (as defined by this buffer's multiplex ID) are removed
        *       from the table, but the table itself is left intact.
        * </ul>
        */
       private class Multiplexer
       implements Finalizable
       {
          /**
           * Constructor.
           *
           * @param   ctx
           *          Persistence context for transaction management.
           */
          Multiplexer(Persistence.Context ctx)
          {
          }

          /**
           * No-op.
           */
          @Override
          public void iterate()
          {
          }

          /**
           * No-op.
           */
          @Override
          public void retry()
          {
          }

          /**
           * Cleans up the backing temp table by either dropping it (if no other
           * buffers are using it any longer), or by removing this buffer's
           * records from it.  If we are not already within a database-level
           * transaction, begin a transaction before making the changes, and
           * commit it afterward.
           */
          @Override
          public void finished()
          {
             // only allow cleanup if the outermost scope opened on this buffer has been closed
             if (getOpenScopeCount() > 0)
             {
                return;
             }

             closeMultiplexScope();
          }

          /**
           * Provides a notification that the external program in which the object is registered is
           * being deleted and the object's reference may be lost after this method is called.
           * <p>
           * This implementation does nothing.
           */
          @Override
          public void deleted()
          {
             // no-op
          }
       }

       /**
        * An object of this class is registered at all blocks within a no-undo buffer's scope, except
        * internal procedures (TODO: further testing is needed to verify that this is correct). Upon
        * each iteration, retry, and exit (finished) event of one of those blocks, it performs a
        * non-destructive validation (i.e., validation which reports any error, but does not alter
        * control flow).
        */
       private class NoUndoValidator
       implements Finalizable
       {
          /**
           * The block is exiting; perform a non-destructive validation of the record in the buffer.
           */
          @Override
          public void finished()
          {
             nonDestructiveValidate();
          }

          /**
           * The block is iterating; perform a non-destructive validation of the record in the buffer.
           */
          @Override
          public void iterate()
          {
             nonDestructiveValidate();
          }

          /**
           * The block is retrying; perform a non-destructive validation of the record in the buffer.
           */
          @Override
          public void retry()
          {
             nonDestructiveValidate();
          }

          /**
           * No-op.
           */
          @Override
          public void deleted()
          {
             // no-op
          }

          /**
           * Validate the current record in the buffer (if any), but do not change the control flow
           * of the block logic in the event of an error; just report the error if not in silent
           * error mode.
           */
          private void nonDestructiveValidate()
          {
             try
             {
                validate(false, false);
             }
             catch (ErrorConditionException exc)
             {
                // TODO: we eat this because we can't allow the exception to unravel control flow,
                // but we need to report the error if not in silent error mode; requires some changes
                // to the TransactionManager or BlockManager infrastructure
                if (LOG.isLoggable(Level.FINE))
                {
                   LOG.log(Level.FINE, "Error validating NO-UNDO temp-table record", exc);
                   if (LOG.isLoggable(Level.FINER))
                   {
                      LOG.log(Level.FINER, TemporaryBuffer.this.toString());
                   }
                }
             }
          }
       }

       /**
        * A class that holds the default UNDO attibute for a TEMP-TABLE. Even for static TEMP-TABLEs,
        * the attribute can be changed at runtime. The lifespan of this object is short, it is created
        * at the moment of the definition of the default buffer, and just until the buffer is open,
        * at which moment this {@link UndoStateProvider} is replaced by the {@link TempTable} that
        * will properly manage the UNDO attribute.
        * This class is immutable and is discarded when not needed any more.
        */
       private static class DefaultUndoState
       implements UndoStateProvider
       {
          /** The default value of the UNDO attribute. */
          final boolean undo;

          /**
           * The constructor. Initializes the internal state.
           * @param   undo
           *          The default value of the default UNDO attribute.
           */
          private DefaultUndoState(boolean undo)
          {
             this.undo = undo;
          }

          /**
           * Accessor for the internal state.
           *
           * @return   The default value of the default UNDO attribute.
           */
          @Override
          public boolean isUndoable()
          {
             return undo;
          }
       }

       /**
        * The handler to switch between references, when binding a definition table to another one.
        */
       private static class ReferenceProxy
       implements InvocationHandler
       {
          /** The default properties - PK and any other properties in {@link TempRecord}. */
          private static final Map<String, String> defaultProps;

          static
          {
             Map<String, String> props = new HashMap<>();
             for (Method m : TempTableRecord.class.getMethods())
             {
                props.put(m.getName(), m.getName());
             }
             props.put(Session.PK, Session.PK);

             defaultProps = Collections.unmodifiableMap(props);
          }

          /** Cache of property names, for each DMO. */
          private static final Map<Class<? extends DataModelObject>, String[]> BOUND_PROPERTIES =
             Collections.synchronizedMap(new IdentityHashMap<>());

          /** Cache of property indexes, for each DMO. */
          private static final Map<Class<? extends DataModelObject>, Map<String, Integer>> PROPERTY_INDEXES =
             Collections.synchronizedMap(new IdentityHashMap<>());

          /**
           * Cache of mappings of definition DMO property names to bound DMO property names.  First key is composed
           * from the definition DMO name, chr(1) and the bound DMO name, as a string.
           */
          private static final Map<String, Map<String, String>> PROPERTIES =
             Collections.synchronizedMap(new HashMap<>());

          /** The definition-time DMO proxy. */
          private final Temporary def;

          /** The buffer instance created at the definition time. This is what {@link #def} proxies. */
          private final TemporaryBuffer defBuffer;

          /** The DMO property setters to their property name. */
          private Map<Method, String> defPropsBySetter = null;

          /** The DMO property getters to their property name. */
          private Map<Method, String> defPropsByGetter = null;

          /** A map between the definition-time DMO properties and their indexes. */
          private Map<String, Integer> propertyIndexes = null;

          /** The bound DMO proxy. */
          private Temporary bound = null;

          /** The bound DMO property names. */
          private String[] boundProperties = null;

          /** The bound DMO property getters. */
          private Map<String, Method> boundGetterByProp = null;

          /** The bound DMO property setters. */
          private Map<String, Method> boundSetterByProp = null;

          /** The bound DMO buffer.  This is what {@link #bound} proxies. */
          private TemporaryBuffer boundBuffer = null;

          /**
           * A map of conversion-time (original) DMO property names to their runtime correspondents.
           */
          private Map<String, String> properties = null;

          /** Force the BUFFER and TEMP-TABLE to be returned from the definition, and not the bound buffer. */
          private boolean forceDefinition = false;

          /**
           * Flag indicating if the current binding must be auto-deleted when the definition buffer gets deleted,
           * as this was created internally by FWD.
           */
          private boolean autoDeleteBinding = false;

          /**
           * Create a new mutable proxy for the given definition-time DMO proxy.
           *
           * @param    def
           *           The definition-type DMO proxy.
           */
          public ReferenceProxy(Temporary def)
          {
             this.def = def;
             this.defBuffer = (TemporaryBuffer) ((BufferReference) def).buffer();
          }

          /**
           * Bind to the specified DMO proxy.
           *
           * @param    table
           *           The new DMO proxy to bound to.
           *
           * @return   The old {@link #bound} DMO proxy.
           */
          public Temporary bind(Temporary table)
          {
             return bind(table, false);
          }

          /**
           * Bind to the specified DMO proxy.
           *
           * @param    table
           *           The new DMO proxy to bound to.
           * @param    autoDeleteBinding
           *           Flag indicating if the bound buffer must be auto-deleted when the definition buffer gets
           *           deleted.
           *
           * @return   The old {@link #bound} DMO proxy.
           */
          public Temporary bind(Temporary table, boolean autoDeleteBinding)
          {
             this.autoDeleteBinding = autoDeleteBinding;

             Class<? extends DataModelObject> defDmoInterface = defBuffer.getDMOInterface();

             defPropsBySetter = PropertyHelper.pojoPropertiesBySetter(defDmoInterface);
             defPropsByGetter = PropertyHelper.pojoPropertiesByGetter(defDmoInterface);

             Temporary old = bound;

             boundBuffer = (TemporaryBuffer) ((BufferReference) table).buffer();
             if (boundBuffer.mutableHandler != null)
             {
                bound = boundBuffer.mutableHandler.bound == null
                           ? boundBuffer.mutableHandler.def
                           : boundBuffer.mutableHandler.bound;
             }
             else
             {
                bound = (Temporary) boundBuffer.getDMOProxy();
             }

             Class<? extends DataModelObject> boundDmoInterface = boundBuffer.getDMOInterface();

             // compute the method association between the definition and the runtime binding

             List<TableMapper.LegacyFieldInfo> boundFields = null;
             boundProperties = BOUND_PROPERTIES.get(boundDmoInterface);
             if (boundProperties == null)
             {
                boundFields = TableMapper.getAllLegacyFieldInfo(boundBuffer.getParentTable());

                boundProperties = new String[boundFields.size()];
                for (int i = 0; i < boundFields.size(); i++)
                {
                   boundProperties[i] = boundFields.get(i).getJavaName();
                }

                BOUND_PROPERTIES.put(boundDmoInterface, boundProperties);
             }

             List<TableMapper.LegacyFieldInfo> cvtFields = null;
             String key = defDmoInterface.toString() + ((char) 1) + boundDmoInterface.toString();
             properties = PROPERTIES.get(key);
             if (properties == null)
             {
                if (boundFields == null)
                {
                   boundFields = TableMapper.getAllLegacyFieldInfo(boundBuffer.getParentTable());
                }

                cvtFields = TableMapper.getAllLegacyFieldInfo(defBuffer.getParentTable());

                properties = new HashMap<>();
                properties.putAll(defaultProps);
                for (int i = 0; i < boundFields.size(); i++)
                {
                   properties.put(cvtFields.get(i).getJavaName(), boundProperties[i]);
                }
                properties = Collections.unmodifiableMap(properties);

                PROPERTIES.put(key, properties);
             }

             propertyIndexes = PROPERTY_INDEXES.get(defDmoInterface);
             if (propertyIndexes == null)
             {
                if (cvtFields == null)
                {
                   cvtFields = TableMapper.getAllLegacyFieldInfo(defBuffer.getParentTable());
                }

                propertyIndexes = new HashMap<>();
                for (int i = 0; i < cvtFields.size(); i++)
                {
                   propertyIndexes.put(cvtFields.get(i).getJavaName(), i);
                }

                PROPERTY_INDEXES.put(defDmoInterface, propertyIndexes);
             }

             boundSetterByProp = PropertyHelper.pojoSettersByProperty(boundDmoInterface);
             boundGetterByProp = PropertyHelper.pojoGettersByProperty(boundDmoInterface);

             return old;
          }

          /**
           * Unbind this mutable proxy.
           */
          public void unbind()
          {
             autoDeleteBinding = false;
             bound = null;
             boundBuffer = null;
             boundProperties = null;
             boundSetterByProp = null;
             boundGetterByProp = null;
             properties = null;
          }

          /**
           * Proxy handler.   It delegates the call to the {@link #bound} buffer and takes care of
           * translating bound buffer's properties to the definition properties (which exist in the
           * converted code).
           * <p>
           * Some method has special routing:
           * <ul>
           *    <li> {@link BufferReference#definition} will always return the {@link #def} buffer.
           *    </li>
           *    <li> {@link BufferImpl#ref()} will always return the runtime instance for this
           *         mutable proxy ({@link #bound} if set, otherwise {@link #def}).</li>
           * </ul>
           */
          @SuppressWarnings("deprecation")
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable
          {
             Object instance = (bound == null ? def : bound);

             Class<?> declClass = method.getDeclaringClass();
             if (declClass.equals(BufferReference.class) && method.getName().equals("definition"))
             {
                return defBuffer;
             }
             if (BufferImpl.class.isAssignableFrom(declClass) && method.getName().equals("ref"))
             {
                return forceDefinition ? def : instance;
             }
             if (forceDefinition && BufferImpl.class.isAssignableFrom(declClass) && method.getName().equals("tableHandle"))
             {
                instance = def;
             }

             if (declClass.equals(BufferReference.class) ||
                 declClass.equals(Object.class)          ||
                 HandleResource.class.isAssignableFrom(declClass))
             {
                return Utils.invoke(method, instance, args);
             }

             if (bound != null && boundBuffer.getDMOInterface() != defBuffer.getDMOInterface())
             {
                if (Temporary.class.isAssignableFrom(declClass))
                {
                   boolean setter = true;
                   String defProperty = defPropsBySetter.get(method);
                   if (defProperty == null)
                   {
                      setter = false;
                      defProperty = defPropsByGetter.get(method);
                   }

                   int defIndex = propertyIndexes.get(defProperty);
                   String boundProperty = boundProperties[defIndex];

                   Method mbound = (setter ? boundSetterByProp.get(boundProperty)
                                           : boundGetterByProp.get(boundProperty));

                   method = mbound;
                }
             }
             // tried to migrate to related Method.canAccess(this), but it does not work as expected
             // added @SuppressWarnings("deprecation") to invoke method to ignore
             // warning until a correct migration is implemented
             boolean accessible = method.isAccessible();
             try
             {
                if (!accessible)
                {
                   method.setAccessible(true);
                }

                return Utils.invoke(method, instance, args);
             }
             catch (Throwable t)
             {
                Throwable cause = t;
                while (cause != null && !(cause instanceof ConditionException))
                {
                   cause = cause.getCause();
                }

                if (cause instanceof ConditionException)
                {
                   throw cause;
                }
                else
                {
                   throw t;
                }
             }
             finally
             {
                if (!accessible)
                {
                   method.setAccessible(false);
                }
             }
          }
       }

       /**
        * Table copy mode.
        */
       public enum CopyTableMode
       {
          /** Table is copied using COPY-TEMP-TABLE. */
          COPY_TEMP_TABLE_MODE,

          /** Table is passed as an INPUT TABLE[-HANDLE] parameter. */
          INPUT_PARAM_MODE,

          /** Table is passed as an OUTPUT TABLE parameter. */
          OUTPUT_TABLE_PARAM_MODE,

          /** Table is passed as an OUTPUT TABLE-HANDLE parameter. */
          OUTPUT_TABLE_HANDLE_PARAM_MODE
       }

       /**
       * A getter for the deleted flag.
       * @return True is the buffer was deleted as a resource.
       */
       public boolean isDeleted()
       {
           return deleted;
       }

        /**
         * A setter for the deleted flag.
         *
         * @param deleted
         *        Boolean value indicating that the buffer was deleted.
         */
       public void setDeleted(boolean deleted)
       {
           this.deleted = deleted;
       }
    }