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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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 "master" 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;
}
}