# BufferImpl.java

    /*
    ** Module   : BufferImpl.java
    ** Abstract : Implementation of Buffer interface.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description----------------------------------------
    ** 001 ECF 20130118 Created initial version. Implementation of the Buffer
    **                  interface; provides services for buffer-related, Progress
    **                  methods, attributes, and some language statements.
    ** 002 VMN 20130207 Added support for handle-based methods
    **                  BUFFER-COPY (KW_BUF_COPY)
    **                  BUFFER-COMPARE (KW_BUF_COMP)
    **                  BUFFER-FIELD (KW_BUF_FLD)
    **                  BUFFER-RELEASE (KW_BUF_REL)
    **                  FIND-BY-ROWID (KW_FIND_BR)
    **                  FIND-UNIQUE (KW_GET_UNI)
    **                  FIND-FIRST (KW_FIND_1ST)
    **                  FIND-LAST (KW_FIND_LST)
    **                  and attributes
    **                  NUM-FIELDS (KW_NUM_FLD)
    **                  COLUMN-LABEL (KW_COL_LAB)
    **                  INITIAL (KW_INIT)
    **                  AMBIGUOUS (KW_AMBIG)
    ** 003 ECF 20130214 Added bufferCreate and variants of bufferCopy.
    ** 004 SVL 20130213 Added disable*Triggers functions.
    ** 005 VMN 20130217 Added support for handle-based attributes:
    **                  CURRENT-ITERATION (KW_CUR_ITER)
    **                  ERROR (KW_ERROR)
    **                  DBNAME (KW_DBNAME)
    **                  TABLE (KW_TABLE)
    ** 006 VMN 20130219 Added support for attributes
    **                  TABLE-HANDLE (KW_TAB_HAND)
    ** 007 CA  20130221 Added support for ADM-DATA, UNIQUE-ID, NAMESPACE-URI. Implemented Deletable.
    **                  Added missing BUFFER-FIELD stubs.
    ** 008 GES 20130224 Added support for a DELETE statement with a validation expression.
    ** 009 ECF 20130224 Added limited support for embedded SQL.
    ** 010 OM  20130222 Added bufferField variants and dereference from Dereferenceable.
    ** 011 OM  20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
    ** 012 VMN 20130306 Corrected support for handle-based methods:
    **                  FIND-UNIQUE (KW_GET_UNI)
    **                  FIND-FIRST (KW_FIND_1ST)
    **                  FIND-LAST (KW_FIND_LST)
    ** 013 VMN 20130311 Corrected support and runtime for handle-based methods:
    **                  FIND-UNIQUE (KW_GET_UNI)
    **                  FIND-FIRST (KW_FIND_1ST)
    **                  FIND-LAST (KW_FIND_LST)
    **                  FIND-BY-ROWID (KW_FIND_BR)
    **                  BUFFER-RELEASE (KW_BUF_REL)
    **                  BUFFER-COMPARE (KW_BUF_COMP)
    ** 014 ECF 20130309 Changed API for bufferCopy and bufferCompare; only wrapped data types are
    **                  allowed for literal parameters, to prevent the explosion of the API.
    ** 015 VMN 20130314 Implemented runtime for handle-based methods:
    **                  BUFFER-COMPARE (KW_BUF_COMP) also noLobs parameter type changed
    **                  BUFFER-COPY (KW_BUF_COPY) (simple signatures)
    **                  First parameter type changed from String to character for
    **                  FIND-UNIQUE (KW_GET_UNI)
    **                  FIND-FIRST (KW_FIND_1ST)
    **                  FIND-LAST (KW_FIND_LST)
    ** 016 CA  20130323 Changed the APIs for KW_BUF_DEL and KW_EMPTY_TT to return logical.
    ** 017 ECF 20130417 Moved initial() method to FieldReference.
    ** 018 VMN 20130426 Implemented runtime for handle-based methods:
    **                  BUFFER-COMPARE (KW_BUF_COMP)
    **                  BUFFER-COPY (KW_BUF_COPY)
    ** 019 CA  20130603 resourceDelete must return a boolean value.
    ** 020 VMN 20130711 Implemented verifyTransaction() and added to handle-based methods:
    **                  BUFFER-CREATE (KW_BUF_CREA)
    **                  BUFFER-DELETE (KW_BUF_DELETE)
    **                  FIND-BY-ROWID (KW_FIND_BR)
    ** 021 VMN 20130717 Added aggregated functions support for embedded SQL.
    ** 022 SVL 20130822 Added DYNAMIC attribute, implemented bufferField.
    ** 023 CA  20130927 Removed unknown() API, as by default the resource is known.
    ** 024 CA  20131021 Do not delete static buffers.
    ** 025 ECF 20131028 Import change.
    ** 026 SVL 20131028 Handled bufferField() for static tables, added doSetName.
    ** 027 CA  20131013 Static buffers are deleted only when the instantiating procedure gets deleted.
    ** 028 OM  20131128 Stub implementation for getUniqueID.
    ** 029 HC  20131222 Implemented ADM-DATA and UNIQUE-ID attribute.
    ** 030 VMN 20140110 Implemented bufferField(character).
    ** 031 OM  20140107 Implemented disableDumpTriggers() and disableLoadTriggers() methods and
    **                  and removed unneeded openReadOnlyScope().
    ** 032 OM  20140116 Implemented attributes and methods (NUM-FIELDS, ERROR, DBNAME, TABLE,
    **                  NAMESPACE-URI, dereference(::), BUFFER-FIELD(int), intermediary step for
    **                  findFirst, findLast, findUnique).
    ** 033 OM  20140122 Added primary index selection for find-by-rowid dynamic finds. Added
    **                  implementation for dynamic finds methods using DynamicQueryHelper.
    ** 034 ECF 20140305 Added defaultBufferHandle implementation, reworked bufferField(String) to
    **                  handle invalid buffer name.
    ** 035 VMN 20140330 Added LockType errors processing.
    ** 036 VIG 20140314 Changed currentIteration() method signature.
    ** 037 OM  20141103 Added doGetName() method.
    ** 038 ECF 20141117 Added stub methods for all raw-transfer variants.
    ** 039 ECF 20150114 Reimplemented valid() to always return true. A buffer whose scope is not open
    **                  is still valid for use.
    ** 040 OM  20150205 Queries do not need to be unregistered from DynamicQueryHelper.
    ** 041 OM  20150302 Fixed bufferField() behavior on invalid field name.
    ** 042 ECF 20150315 Replaced FieldReference with BufferFieldImpl for BUFFER-FIELD use cases.
    ** 043 OM  20150303 Fixed hasNext/PrevSibling(). Implemented special resource chaining for
    **                  BUFFERs (TEMP-TABLEs in front of permanent TABLE buffers).
    ** 044 OM  20150506 Updated javadoc for deleteAll() and delete() methods.
    ** 045 CA  20151219 Implicit buffers for a defined global shared var can not be deleted.  Also,
    **                  deleted buffers must be tracked explicitly.
    ** 046 EVL 20160223 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 047 ECF 20160219 Added checkExtentRange and outOfBoundsError, methods which are called from
    **                  DMO proxies. Reimplemented _numFields.
    ** 048 EVL 20160325 Javadoc fixes.
    ** 049 ECF 20160404 Javadoc fixes.
    ** 050 OM  20160302 Dropped errors generated inside _find() methods.
    **         20160507 Partially reverted dropping of _find() errors to fix regressions.
    ** 051 SVL 20160510 Support for table-level delete validation.
    ** 052 ECF 20160517 Modified findByRowID to deal with null primary index more efficiently.
    ** 053 GES 20160530 Validation processing now uses lambdas instead of BufferValidator instances.
    ** 054 OM  20160623 Small change in error message.
    ** 055 SVL 20160705 Added indexInformation method.
    ** 056 IAS 20160714 Use legacy name instead of DMO alias.
    ** 057 ECF 20160829 Performance enhancement: use local BufferManager to check transaction status
    **                  instead of TransactionManager.
    ** 058 IAS 20161907 Resolve P2JQuery.Parameter in delete(String, Object...)
    ** 059 ECF 20171016 Implemented readXml and writeXml methods.
    ** 060 GES 20171206 Switch to ErrorManager.silent().
    **     ECF 20171212 Remove redundant query substitution parameter preprocessing for bulk delete.
    ** 061 OM  20171216 Added delete() variant with support for external buffers for nested selects.
    **     CA  20171220 Cleanup the SharedVariableManager.bufferCreator on buffer delete.
    ** 062 GES 20180419 Added the RAW-TRANSFER() legacy method.
    ** 063 OM  20181114 Added NamedSerializable interface implementation.
    ** 064 ECF 20181208 Added stubs for recordLength, findCurrent, readJson, writeJson methods.
    ** 065 OM  20181206 Added QUERY readonly attribute for BUFFERs.
    ** 066 OM  20190117 Added CURRENT-CHANGED, FIND-CURRENT. Fixed locking in _find.
    **     ECF 20190118 Added basic runtime implementation of readJson, writeJson.
    **     OM  20190206 Added implementation of recordLength().
    **     OM  20190213 Added error messages in dereference().
    **     ECF 20190212 Added no-lobs implementation to bufferCopy, bufferCompare methods.
    **     OM  20190228 Removed deleted buffers from dynamic queries.
    ** 067 OM  20190327 Renamed DataSource to avoid conflicts with DataSet source.
    ** 068 CA  20190423 Added dereference(Class, String), used for chained :: operators.
    **     OM  20190423 Implemented copyTempTable() methods.
    ** 069 CA  20190511 Added API for BUFFER-FIELD(POLY).
    **                  Added BUFFER-VALIDATE stub.
    ** 070 SVL 20190614 Reflected changes in TableMapper.
    ** 071 OM  20190426 Extracted hasField() and getHibernateField() methods to avoid code duplication.
    **     OM  20190510 Clarified generation of methods and attributes for variables with BUFFER /
    **                  TEMP-TABLE ABL namespaces specified. Implemented several attributes.
    **     HC  20190707 Implemented read(dataSet) and read(tempTable) methods of JsonObject legacy
    **                  class.
    ** 072 ECF 20190813 Change to RecordBuffer.release API.
    ** 073 OM  20190720 Added BATCH FILL support. FILL returns FALSE and stops if a child FILL fails.
    **                  Fixed CamelCase Sensitivity. Fixed FILL with DATA-SOURCE with provided query.
    **     CA  20190728 If a callback raises an ERROR condition, invokeCallback must return false.
    **                  Also, any FILL operation must be aborted.
    **     CA  20190812 Allow this class to be mutable, to allow switching between resources, in cases
    **                  of BY-REFERENCE or REFERENCE-ONLY.  WARNING: it is not allowed to access
    **                  instance fields, if they are in another instance than 'this' reference!
    **                  Use the 'ref()' API to get the runtime instance or add the field to an
    **                  accessor method.
    **     OM  20190906 Added support for MERGE and REPLACE fill modes. Added ERROR-STRING and
    **                  ERROR-FLAG attributes support.
    ** 075 CA  20191119 Fixed some issue with SAVE-ROW-CHANGES: any ERROR message prior to invocation
    **                  must not affect the result; fixed the 'source was not changed in the database'
    **                  check, where the source and before buffer must be checked.
    ** 076 AIL 20191128 Moved the validation phase in RecordBuffer.
    ** 077 AIL 20200107 Added name override and restore for temporarily changing name with proxy.
    **     AIL 20200108 Prevented direct access to record buffer members
    **     CA  20200110 Fixes for static buffer resource delete.
    ** 078 IAS 20200221 Reworked RAW-TRANSFER method support.
    ** 079 IAS 20200414 bufferValidate() method implementation.
    **     CA  20200427 Allow bulk assign support for BUFFER-VALUE and ::, in case of extent fields.
    **     CA  20200428 Added LAST-BATCH runtime stub.
    **     CA  20200503 Added BUFFER:IS-MULTI-TENANT runtime stub.
    ** 080 ECF 20200419 Removed Hibernate dependencies.
    **     CA  20200714 The rowState is available even for temp-tables not part of a dataset.
    ** 081 GES 20200807 Reduced ErrorManager context-local usage.
    **     CA  20200910 Added BUFFER:SERIALIZE-ROW() runtime stub.
    **         20200911 attachDataSource allows empty include/excludeFields values.
    **         20200914 attachDataSource allows ROWID function for source field.
    **     OM  20201016 Added implementation for LAST-BATCH, DATA-SOURCE-MODIFIED.
    **     OM  20201030 Invalid attribute API support for getters/setters.
    **     OM  20201202 Fixed persistence support for ACCEPT-CHANGES and REJECT-CHANGES methods.
    **     OM  20201205 Added AUTO-DELETE attribute implementation.
    **     OM  20201218 Fixed implementation for error and rejected attributes/hidden fields.
    **     OM  20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to local
    **                  dmoMeta.legacyTable. Fixed implementation of validate() method/statement.
    **     OM  20210223 Invalidate index-based cache in the event of low-level SQL operations.
    **     OM  20210309 Do not use DmoMeta as key in TableMapper because temp-tables might share the same
    **                  DmoMeta instance.
    **     ECF 20210506 Use RecordBuffer.getDmoInfo() getter instead of direct field access to prevent
    **                  NPE in proxy case.
    **     CA  20210509 Fixed 'defaultTypeMapping' implicit value for readXml - it must be the unknown value, not
    **                  the "?" string.
    **     AIL 20210511 Ensure field string names are normalized before searching.
    **     OM  20210521 DELETE and CREATE return the proper logical value.
    **     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.
    **     CA  20210629 Added dereference operator for indexed extent fields (rvalue and lvalue).
    **     OM  20210809 Completed implementation of SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray).
    **     ECF 20210820 Retrofit for query substitution parameter preprocessing fixes.
    **     OM  20210921 Added missing implementations for NAMESPACE-PREFIX and XML-NODE-NAME attribute.
    **     ECF 20210924 Minor refactoring.
    **     CA  20210929 Fixed ATTACH-DATA-SOURCE when rowid function is used in the pairs.
    **     OM  20211023 Fixed FILL for EXTENT fields.
    **     AL2 20211105 Character values provided by dereference are case insensitive.
    **     ECF 20211203 Fixed error checking in _find method.
    **     OM  20211206 Improve implementation of DATA-SOURCE-COMPLETE-MAP attribute. Fixed FILL assignments.
    **     OM  20211020 Added _DATASOURCE_ROWID property.
    **     CA  20211228 BUFFER:IS-MULTI-TENANT attribute must return false, even if is not implemented - unknown
    **                  is not an acceptable returned value for this attribute.
    **                  Replaced the UnimplementedFeature.missing from tenant-related APIs with a TODO comment.
    **     CA  20211204 Fixed BUFFER-FIELD behavior, they need to behave like real resources, with their lifetime
    **                  linked with the parent BUFFER resource.
    **     OM  20220218 When saving DS changes, allow Session.PK to be resolved instead of dereferencing it.
    **                  Javadoc update.
    **     OM  20220224 Small fixed of the error message for invalid WHERE clause.
    **     OM  20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
    **     OM  20220620 Added missing features in ATTACH-DATA-SOURCE method.
    **     OM  20220707 Shared meta-knowledge about fields and properties of a record was collected in
    **                  DmoMeta to avoid duplicating their collection and storage.
    **     CA  20220707 Create the BUFFER-FIELD resources only when they are accessed.
    **     OM  20220801 Allow unqualified field names in attachDataSource().
    **     OM  20220810 Fixed check for changes in saveRowChangesImpl2() to include extent fields.
    **     OM  20221003 Added missing STREAM targets for JSON/XML serializations.
    **     CA  20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
    **                  allow caching of the resolved target.
    **     CA  20221006 Added JMX instrumentation for 'resourceDelete'. Refs #6814
    **                  UNIQUE-ID and ADM-DATA are kept as Java types instead of BDT.  Refs #6827
    **                  BUFFER-FIELD's instantiating procedure must be the BUFFER's, and not when it gets lazily
    **                  created.
    **     CA  20221010 Reduced 'toLowerCase' usage in 'attachDataSource'.  Refs #6820
    **     TJD 20220504 Upgrade do Java 11 minor changes
    **     CA  20220426 Added PRIMARY attribute stubs.
    **     OM  20220526 Added KEYS and SYNCHRONIZE implementations.
    **     CA  20220531 If the BUFFER is in 'unknown mode' (due to an outer join not finding a record), do not
    **                  raise 'no record is available' ERROR condition.
    **     CA  20220601 Added 'derefPoly', emitted when '::' is used as r-value in a dynamic extent assignment.
    **     OM  20220603 Fixed the order of validation for DATA-SOURCE-ROWID getter.
    **     OM  20220604 Improved dereferenciation support.
    **     OM  20220607 Added EXTENT support for SAVE-ROW-CHANGES.
    **     CA  20220613 Fixed RAW-TRANSFER via 'rawCopyFrom(Buffer)' - the data was being read twice from the
    **                  stream.  Also, the dest buffer creates a new record only if there isn't one already loaded.
    **     OM  20220617 Improve DATA-SOURCE-ROWID implementation.
    **     OM  20220620 Added missing features in ATTACH-DATA-SOURCE method and DATA-SOURCE-ROWID attribute.
    **     OM  20220609 Fixes for several 4GL attributes.
    **     CA  20220909 When a query gets deleted and is set at this buffer's 'lastQuery', remove the reference.
    **     CA  20220918 Always delete the REPLACE dynamic query at the FILL operation.
    **     CA  20221010 Reduced 'toLowerCase' usage in 'attachDataSource'.  Refs #6820
    **     CA  20221121 For a dynamic TEMP-TABLE, if the NAME attribute is changed, then the default-buffer's NAME
    **                  must be changed, too.
    **     SVL 20230108 Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     RAA 20230109 Changed inline statement(s) to prepared statement(s).
    **     CA  20230110 Do not create a 'callbacks' map until the application actually sets a callback.
    **     CA  20230110 UNIQUE-ID is generated only when is read by the application.
    **                  Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
    **                  context-local lookup.
    **     SVL 20230110 P2JIndex.components() provides direct access to the components array in order to improve
    **                  performance.
    **     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.
    ** 082 CA  20230303 When deleting a temp-table, the delete can be vetoed if any buffer for the temp-table is
    **                  associated with an OUTPUT dataset.  Refactored to move the resource-specific code in a
    **                  'allowDelete' method.
    ** 083 CA  20230215 Improved other cases to reduce BDT usage from within FWD runtime.
    **     CA  20230303 CA/20190720 change in HandleOps.delete related to postponing a temp-table delete if is an
    **                  OUTPUT argument is currently flawed, is it compares handle instances instead of the
    **                  handle's resource.  For this reason, this code was commented out.
    ** 084 OM  20230320 bufferFieldNative() use the original property name, not a random one from denormalized set.
    ** 085 IAS 20230405 Fixed FILL support for recursive DATA-RELATION
    **                  Added support for the RESTART-ROW attribute.
    **                  Added support for the SYNCHRONIZE method.
    ** 086 IAS 20230325 Added source validation to readJson().
    **     IAS 20230501 Added 'emergency exit' for WRITE-XML
    ** 087 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 088 RAA 20230525 Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
    ** 089 CA  20230523 Performance improvement of the buffer fill operation - when possible (data type match),
    **                  both source and destination buffers), use direct access to read and write the value.
    **                  Extent fields are not supported yet.
    ** 090 CA  20230607 TRANSACTION-MODE AUTO must preserve the tx in the root appserver block - this is required
    **                  because in modes other than State-free, requests can be executed on the same agent who had
    **                  TRANSACTION-MODE AUTO, and this must see as active the tx initially set.
    ** 091 CA  20230701 In FILL, if the source buffer has no record, do nothing, and allow the record to use the
    **                  default values.
    ** 092 AL2 20230704 Avoid .buffer() calls in attachDataSource, so precomupte the dmo meta for each buffer.
    ** 093 OM  20230712 Reworked the DATA-SOURCE-COMPLETE-MAP and the related methods and attributes.
    ** 094 CA  20230724 Further reduce context-local usage.
    ** 095 CA  20230815 Ensure a dynamic query created by the FWD runtime during a FILL operation gets deleted
    **                  (this is the case when the data-source does not specify a query handle).
    ** 096 CA  20230817 A dynamic BUFFER part of a dataset can't be explicitly deleted.
    ** 097 EVL 20230817 Adding GET-ITERATION implementation stub.
    ** 098 CA  20230828 If 'parentDataSource' is invalid, allow attaching another datasource.
    ** 099 CA  20231114 ERROR conditions via ErrorManager API calls must be followed by a return statement.
    ** 100 AD  20231127 Optimized bufferFieldnative(int) to directly use DmoMeta to get name of property.
    ** 101 OM  20231031 Added error handling in MERGE-CHANGES and MERGE-ROW-CHANGES methods.
    ** 102 CA  20231129 Directly set the INSTANTIATING-PROCEDURE referent, from the parent BUFFER's, instead of
    **                  passing it as a handle.
    **     CA  20231221 Lazily create fieldsByName map.
    ** 103 RAA 20231220 Rewrote extent value parsing in assign. Modified two loops to be index-type ones.
    ** 104 OM  20240110 Fixed management of implicit/explicit FILL queries.
    ** 105 OM  20240109 Before writing a JSON/XML all buffers of the temp-tables must be flushed. After the
    **                  operation ends the used buffer is released.
    ** 106 CA  20240206 Error 11874 must not set ERROR-STATUS:ERROR flag.
    ** 107 HC  20240222 Enabled JMX on FWD Client.
    ** 108 AL2 20240307 Mark the query to retrieve the entire result set (useful for FILL operations).
    ** 109 CA  20240320 Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for', where it
    **                  applies.
    ** 110 CA  20240323 Performance optimization to cache the FieldStructure for each legacy field, during FILL
    **                  operations.
    **     CA  20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
    **     CA  20240331 Use a plain HashMap with strings for the FieldStructure cache, to avoid recomputing the
    **                  hashCode.
    ** 111 OM  20240401 When attaching a data-source with explicit query, the latter is configured with the
    **                  substitution buffers from this buffer's dataset.
    ** 112 CA  20240404 Errors 12360, 12780, 12781, 14275, 11935, 11906, 11909, 11912, 11913 do not set the
    **                  ERROR-STATUS:ERROR flag.
    ** 113 CA  20240411 Flush is done for all buffers in 'copyChanges' so validation is no longer needed for
    **                  'getChanges'.
    ** 114 CA  20240410 SAVE-ROW-CHANGES must use only the data-source fields which are belong to the source
    **                  buffer.
    ** 115 AL2 20240412 Enforce released record flushing, overruling the fact that is may be referenced by other
    **                  buffers.
    ** 116 CA  20240424 The buffers on which the DATA-SOURCE is attached must be available to any FIND- queries.
    ** 117 CA  20240422 Allow the query to be reset to its initialization state, so the same instance can be used
    **                  to execute child buffer FILL operations.
    ** 118 CA  20240525 Bulk delete can't be executed if TRACKING-CHANGES is on, FWD runtime needs to emulate an
    **                  actual FOR EACH to delete all records.
    ** 119 SB  20240528 Fixed a regression where honorMultipleReferences was honored when releasing a dynamic
    **                  buffer preventing, its flush. Refs #8760.
    ** 120 DDF 20240523 Added an additional parameter to displayError() call in dereference().
    **     DDF 20240524 Replaced displayError() with recordOrShowError() in dereference().
    **     DDF 20240527 Replaced recordOrShowError() with recordOrShowDatabaseError() where possible.
    **     DDF 20240428 Reverted changes and added only the ones tested for database behavior.
    ** 121 TJD 20240124 Java 17 - checkExtentRange and setQuery need to be public to enable access from generated
    **                  code
    ** 122 SP  20240603 Fixed error handling when FILL fails with unique validation error in APPEND mode.
    ** 123 SP  20240606 Error 4065 must not set ERROR-STATUS:ERROR flag.
    ** 124 CA  20240627 Always register the buffer as dirty for the batch fill operation, as direct access
    **                  bypasses the proxy call in RecordBuffer.
    ** 125 CA  20240723 ACCEPT-CHANGES must not prune the session cache, but update the before-fields for the
    **                  cached records directly.
    ** 126 SP  20240805 Skip using RESOURCE_DEL TimeStat if JMX_DEBUG flag is not set.
    ** 127 CA  20240918 Removed default methods from interfaces and moved them as concrete implementations, as
    **                  default methods cause the Java metaspace to spike one order of magnitude.
    ** 128 OM  20241001 Implemented tenant-related attributes.
    ** 129 SP  20241029 Added NPE failsafe for copyTempTable and deleteAll. Javadoc fixes.
    ** 130 CA  20241030 Cache the BUFFER-FIELD handles.
    ** 131 AI  20241107 Fixed a regression in H130 - 'bufferField' must validate the index.
    ** 132 ES  20241030 Changed default onPhrase in the doBlock in saveRowChangesImpl2 method.
    ** 133 OM  20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
    **                  based on [sharedDb] parameter.
    ** 134 AS  20241203 Introduced areBDTsEqual, used to detect changes in saveRowChanges.
    ** 135 ICP 20250123 Used integer and logical constants to leverage caches instances.
    ** 136 ES  20250312 Added label methods.
    ** 137 ES  20250312 Added methods autoSynchronize and canRead methods.
    ** 138 CA  20250321 Added 'setCallback' overloads.
    ** 139 ES  20250508 Added runtime support for AUTO-SYNCHRONIZE buffer attribute.
    ** 140 ES  20250313 Moved NAMESPACEURI, NAMESPACEPREFIX, SERIALIZENAME, XMLNODENAME attributes
    **                  to buffer instance.
    */

    /*
    ** 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 java.io.*;
    import java.util.*;
    import java.util.function.*;
    import java.util.logging.Level;
    import java.lang.reflect.*;
    import com.goldencode.p2j.*;
    import com.goldencode.p2j.jmx.*;
    import com.goldencode.p2j.persist.event.*;
    import com.goldencode.p2j.persist.lock.*;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.trigger.*;
    import com.goldencode.p2j.persist.serial.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.BlockManager.TransactionType;
    import com.goldencode.p2j.util.ErrorManager.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.p2j.util.UniqueIdGenerator.*;
    import com.goldencode.p2j.persist.TempTable.BeforeType;
    import com.goldencode.util.*;
    import org.apache.commons.lang3.tuple.*;

    import static com.goldencode.p2j.util.BlockManager.*;

    /**
     * This class implements services representing Progress builtin functions,
     * methods, attributes, and language statements, which operate on record
     * buffers.
     * <p>
     * Converted business logic invokes these methods on DMO proxies via a
     * DMO-specific interface in the form <code>&lt;DMO interface&gt;.Buf</code>.
     * Each such interface extends both a custom interface of DMO-specific getter
     * and setter methods, as well as the {@link Buffer} interface, which defines
     * the API for these standard services.
     * <p>
     * Dynamic proxy classes for each DMO type extend this class, so that business
     * logic can invoke these standard services directly as method calls on DMO
     * proxy instances.
     * <p>
     * This class is intentionally package private, since it is not meant to be
     * extended outside of this package (and within this package only by dynamic
     * proxy class), nor instantiated directly.
     * <p>
     * This class implements {@link BufferReference} in order to grant its
     * instance methods access to the {@link RecordBuffer} object associated with
     * each instance of the DMO proxy class which extends this class. This access
     * is necessary for these methods to do their work. Even though {@link
     * #buffer()} is a public method, external packages will not be able to invoke
     * this method (except through reflection). This is by design, as business
     * logic is not meant to have access to <code>RecordBuffer</code> instances.
     * The default implementation of this method here throws an exception, as it
     * is never intended to be called, but it needs to be present in order for
     * this class to compile, since it is referenced by other methods. Each DMO
     * proxy class overrides this method to provide a working implementation.
     */
    public abstract class BufferImpl
    extends HandleChain
    implements Buffer
    {
       /** Instrumentation for {@link #resourceDelete()}. */
       private static final NanoTimer RESOURCE_DEL = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmBufferDelete);

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

       /** The call-site for the {@link Buffer#BEFORE_FILL_EVENT} event callback. */
       private static final InvokeConfig BEFORE_FILL_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#AFTER_FILL_EVENT} event callback. */
       private static final InvokeConfig AFTER_FILL_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#BEFORE_ROW_FILL_EVENT} event callback. */
       private static final InvokeConfig BEFORE_ROW_FILL_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#AFTER_ROW_FILL_EVENT} event callback. */
       private static final InvokeConfig AFTER_ROW_FILL_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#ROW_CREATE_EVENT} event callback. */
       private static final InvokeConfig ROW_CREATE_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#ROW_DELETE_EVENT} event callback. */
       private static final InvokeConfig ROW_DELETE_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#ROW_UPDATE_EVENT} event callback. */
       private static final InvokeConfig ROW_UPDATE_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#FIND_FAILED_EVENT} event callback. */
       private static final InvokeConfig FIND_FAILED_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link Buffer#SYNCHRONIZE_EVENT} event callback. */
       private static final InvokeConfig SYNCHRONIZE_CALL_SITE = new InvokeConfig();

       /**
        * Special 'pointer' used in management of BUFFER resources. This helps delimit the TEMP-tables
        * from PERMANENT ones in the list of BUFFERs. The TEMP-TABLE buffers are kept always in front
        * of the list.
        * To be sure there are no conflicts with legacy resource types declared in
        * {@link LegacyResource}, this type starts with an underscore.
        */
       private static final String PERMANENT_TABLE = "_PERMANENT-TABLE";

       /** Corresponds to ADM-DATA attribute. */
       private String admData = null;

       /** Corresponds to UNIQUE-ID attribute. */
       private Long uniqueID = null;

       /** Flag indicating this buffer was deleted. */
       private boolean deleted;

       /** Flag indicating that the deletion of this buffer is in progress. */
       private boolean deleting;

       /** The last query this buffer was added/set to. */
       private QueryWrapper lastQuery = null;

       /** The current dataset. May be {@code null}. */
       private DataSet dataSet = null;

       /** The attached {@code DataSource}, if any. */
       private DataSource dataSource = null;

       /** The current value of BATCH-SIZE attribute. Valid only when {@code dataSet} is valid. */
       private int batchSize = 0;

       /** Current value of the {@code DATA-SOURCE-MODIFIED} attribute. */
       private boolean dataSourceModified = false;

       /** The current value of LAST-BATCH attribute. Valid only when {@code dataSet} is valid. */
       private boolean lastBatch = false;

       /**
        * The current value of the {@code FILL-MODE} attribute. The default value is {@code MERGE}.  For buffers
        * not part of a dataset, {@link #fillMode()} will return the unknown value.
        */
       private String fillMode = FM_MERGE;

       /** The current value of AUTO-DELETE attribute. */
       private boolean autoDelete = true;

       /**
        * Flag used for bracketing internal management of BEFORE-BUFFER. ABL programmer's direct
        * actions (CREATE/DELETE/ALTER buffer) are outside this bracket and will lead to errors.
        */
       private boolean setUpBeforeBuffer = false;

       /** Is this an AFTER-BUFFER or BEFORE-BUFFER? Cached after value is computed. */
       private BeforeType beforeBufferType = BeforeType.UNKNOWN;

       /** The current value of the {@code ATTACHED-PAIRLIST} attribute. */
       private String attachedPairlist = null;

       /** The current value of the {@code DATA-SOURCE-COMPLETE-MAP} attribute. */
       private String dataSrcMappingStr = null;

       /** The mapping of fields in data source. */
       private List<Pair<String, DataSource.FieldReference>> dataSrcMapping = null;

       /**
        * The property meta for the destination buffer. The key is the legacy name.  This is set when the
        * data-source is assigned.
        */
       private Map<String, PropertyMeta> dstFieldMeta = null;

       /** A map with all known callbacks. The keys MUST be in UPPERCASE. */
       private Map<String, CallbackData> callbacks = null;

       /** This is not null, when a Buffer proxy overrides the name and a backup is required */
       private String oldName;

       /** The list of buffer fields. */
       private BufferFieldImpl[] fields = null;

       /** Cache of handle references to return.  The assumption is they are read-only. */
       private handle[] fieldHandles = null;

       /** A mapping of fields by their property name. */
       private HashMap<String, BufferFieldImpl> fieldsByName = null;

       /**
        * The cached peer buffer. For AFTER-BUFFER it is the BEFORE-BUFFER and vice-versa. The SIMPLE
        * buffers do not have a peer buffer.
        */
       protected BufferImpl peerBuffer = null;

       /** The parent {@code DataSource}, if any. This is differnet from the attached datasource! */
       private DataSource parentDataSource = null;

       /** The list of keys, if the buffer is a component of a {@code DataSource}. */
       private List<String> keys = null;

       /** The NAMESPACE-URI of the XML node for serialized data of this {@code TEMP-TABLE}. */
       private String namespaceURI = null;

       /** The NAMESPACE-PREFIX of the XML node for serialized data of this {@code TEMP-TABLE}. */
       private String namespacePrefix = null;

       /** The current value of the {@code SERIALIZE-NAME} attribute. */
       private String serializeName = null;

       /** The name of the XML node for serialized data of this {@code TEMP-TABLE}. */
       private String xmlNodeName = null;

       /**
        * The buffer's current FILL query, which is reset on each sub-fill executed for this buffer.  Will be
        * closed only when the complete FILL operation completes.
        */
       private QueryWrapper reusableQuery;

       /** The current value of AUTO-SYNCHRONIZE attribute. Valid only when {@code dataSet} is valid. */
       private boolean autoSynchronize = false;

       /** The mode for finding (first, last, unique) records in a dynamic call to a buffer handle. */
       enum FindMode
       {
          FIRST,   /* apply first() to a query */
          LAST,    /* apply last() to a query */
          UNIQUE   /* apply unique() to a query */
       }

       /**
        * The default constructor. Calls the super with appropriate parameter values.
        */
       protected BufferImpl()
       {
          super(false, false);
       }

       /**
        * Worker static method for wrapping ErrorConditionException and reusing
        * in all P2JQuery.findFirst() implementations
        *
        * @param   findQuery
        *          instance of FindQuery
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        */
       private static logical wrapFirst(FindQuery findQuery, LockType lockType)
       {
          boolean success = true;
          try
          {
             findQuery.first(lockType);
          }
          catch (ErrorConditionException exc)
          {
             success = false;
          }
          return logical.of(success);
       }

       /**
        * Worker static method for wrapping ErrorConditionException and reusing
        * in all P2JQuery.findLast() implementations
        *
        * @param   findQuery
        *          instance of FindQuery
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       private static logical wrapLast(FindQuery findQuery, LockType lockType)
       {
          boolean success = true;
          try
          {
             findQuery.last(lockType);
          }
          catch (ErrorConditionException exc)
          {
             success = false;
          }
          return logical.of(success);
       }

       /**
        * Worker static method for wrapping ErrorConditionException and reusing
        * in all P2JQuery.findUnique() implementations
        *
        * @param   findQuery
        *          instance of FindQuery
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       private static logical wrapUnique(FindQuery findQuery, LockType lockType)
       {
          boolean success = true;
          try
          {
             findQuery.unique(lockType);
          }
          catch (ErrorConditionException exc)
          {
             success = false;
          }
          return logical.of(success);
       }

       /**
        * Checks if transaction is active otherwise records or shows error message.
        *
        * @param   buffer
        *          Record buffer.
        *
        * @return  <code>true</code> if a transaction is active else <code>false</code>.
        */
       private static boolean verifyTransaction(RecordBuffer buffer)
       {
          if (buffer.getBufferManager().isTransaction())
          {
             return true;
          }
          else
          {
             String msg = "Unable to CREATE/DELETE unless a TRANSACTION is running";
             ErrorManager.recordOrShowError(7345, msg, false);

             return false;
          }
       }

       /**
       * Get the {@link RecordBuffer} instance associated with this object.
       * <p>
       * This implementation exists only to satisfy the Java compiler. This
       * method is only meaningful when a proxy DMO class is created by {@link
       * RecordBuffer}, which will implement this method to return an instance
       * of <code>RecordBuffer</code> associated with the proxy DMO instance.
       *
       * @return  Record buffer associated with this object.
       */
       public abstract RecordBuffer buffer();

       /**
        * Resolve the actual resource used by the runtime.
        *
        * @return   The instance.
        */
       public BufferImpl ref()
       {
          return this;
       }

       /**
        * Get the PRIMARY attribute.
        *
        * @return   The primary index.
        */
       @Override
       public character primaryIndex()
       {
          P2JIndex primaryIndex = buffer().getDmoInfo().getPrimaryIndex(true);
          if (primaryIndex == null)
          {
             return new character("default");
          }

          return new character(primaryIndex.getLegacyName());
       }

       /**
        * Set the PRIMARY attribute.
        *
        * @param    primary
        *           The primary index.
        */
       @Override
       public void primaryIndex(String primary)
       {
          if (primary == null)
          {
             ErrorManager.recordOrShowError(4083, "PRIMARY", LegacyResource.BUFFER + " widget");
             // **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>. (4083)
             return;
          }

          ErrorManager.recordOrShowError(4052, "PRIMARY", "setable", LegacyResource.BUFFER + " widget");
          // **<attribute> is not a <settable/queryable> attribute for <widget id>. (4052)
       }

       /**
        * Set the PRIMARY attribute.
        *
        * @param    primary
        *           The primary index.
        */
       @Override
       public void primaryIndex(Text primary)
       {
          primaryIndex(primary.toJavaType());
       }

       /**
        * Get the BUFFER:LAST-BATCH attribute.
        *
        * @return  the value of BUFFER:LAST-BATCH attribute.
        */
       @Override
       public logical lastBatch()
       {
          return (dataSet == null) ? logical.UNKNOWN : logical.of(lastBatch && batchSize > 0);
       }

       /**
        * Set the BUFFER:LAST-BATCH attribute.
        *
        * @param    lastBatch
        *           The flag state.
        */
       @Override
       public void lastBatch(boolean lastBatch)
       {
          if (dataSet == null)
          {
             ErrorManager.recordOrShowError(12360, false);
             return;
          }

          // all checked, can set value now:
          this.lastBatch = lastBatch;
       }

       /**
        * Set the BUFFER:LAST-BATCH attribute.
        *
        * @param    lastBatch
        *           The flag state.
        */
       @Override
       public void lastBatch(logical lastBatch)
       {
          if (lastBatch == null || lastBatch.isUnknown())
          {
             ErrorManager.recordOrShowError(4083, "LAST-BATCH", "BUFFER widget");
             return;
          }

          lastBatch(lastBatch.booleanValue());
       }

       /**
        * Get the BUFFER:AUTO-DELETE attribute.
        *
        * @return  the value of BUFFER:AUTO-DELETE attribute.
        */
       @Override
       public logical autoDelete()
       {
          if (!_dynamic() || dataSet == null)
          {
             return logical.UNKNOWN;
          }

          return logical.of(this.autoDelete);
       }

       /**
        * Set the BUFFER:AUTO-DELETE attribute.
        *
        * @param    autoDelete
        *           The flag state.
        */
       @Override
       public void autoDelete(boolean autoDelete)
       {
          this.autoDelete = autoDelete;
       }

       /**
        * Set the BUFFER:AUTO-DELETE attribute.
        *
        * @param    autoDelete
        *           The flag state.
        */
       @Override
       public void autoDelete(logical autoDelete)
       {
          if (autoDelete == null || autoDelete.isUnknown())
          {
             ErrorManager.recordOrShowError(4083, "AUTO-DELETE", LegacyResource.BUFFER + " widget");
             // **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>.
             return;
          }

          autoDelete(autoDelete.booleanValue());
       }

       /**
        * Checks whether the data in the data source has been modified.
        *
        * @return  {@code true} when the data has been modified.
        */
       @Override
       public logical isDataSourceModified()
       {
          return logical.of(dataSourceModified);
       }

       /**
        * Manually mark the data in the target structure as modified.
        *
        * @param   mod
        *          {@code true} when the data is marked as modified and {@code false} otherwise.
        */
       @Override
       public void setDataSourceModified(logical mod)
       {
          if (mod == null || mod.isUnknown())
          {
             unableToAssignUnknown("DATA-SOURCE-MODIFIED", "DATASET widget");
             return;
          }

          setDataSourceModified(mod.getValue());
       }

       /**
        * Manually mark the data in the target structure as modified.
        *
        * @param   mod
        *          {@code true} when the data is marked as modified and {@code false} otherwise.
        */
       @Override
       public void setDataSourceModified(boolean mod)
       {
          this.dataSourceModified = mod;
       }

       /**
        * Reports if this object is valid for use.
        *
        * @return  the state of the {@link #deleted} flag.  If a record buffer has been defined, it
        *          is valid for use until is deleted.
        */
       public boolean valid()
       {
          return !deleted;
       }

       /**
        * Check whether the delete operation for this buffer is in progress.
        *
        * @return  {@code true} when the delete operation for this buffer is under way.
        */
       public boolean isDeleting()
       {
          return deleting;
       }

       /**
        * Obtain the handle to the buffer that was created by default for the temporary table backing
        * this buffer. Note that this implementation presumes that this is a temp-table buffer. It
        * should not be invoked on a permanent table buffer.
        *
        * @return  Default buffer for the temporary table backing this buffer, or an unknown handle if
        *          that resource is not valid.
        */
       public handle defaultBufferHandle()
       {
          TempTable tempTable = buffer().getParentTable();
          if (tempTable == null || !tempTable.valid())
          {
             return new handle();
          }

          return tempTable.defaultBufferHandle();
       }

       /**
        * Obtain the handle to the buffer that was created by default for the temporary table backing
        * this buffer. Note that this implementation presumes that this is a temp-table buffer. It
        * should not be invoked on a permanent table buffer.
        *
        * @return  Default buffer for the temporary table backing this buffer, or an unknown handle if
        *          that resource is not valid.
        */
       @Override
       public Buffer defaultBufferHandleNative()
       {
          TempTable tempTable = buffer().getParentTable();
          if (tempTable == null || !tempTable.valid())
          {
             return null;
          }

          return tempTable.defaultBufferHandleNative();
       }

       /**
        * Get the record ID of the current buffer's current record, if any.  P2J
        * implementation of the RECID function.
        * <p>
        * This method will return a 32-bit recid which represents the internal
        * row ID of the given record.  If the actual row ID (which is implemented
        * as a wider integer) cannot fit due to overflow, an error condition will
        * be raised.
        *
        * @return  Record ID of the underlying data record. If no record currently
        *          backs the buffer associated with <code>dmo</code>, a recid
        *          initialized as unknown value is returned.
        *
        * @throws  ErrorConditionException
        *          if the actual record ID is too wide to fit within a 32-bit
        *          integer.
        */
       public recid recordID()
       {
          return buffer().recordID();
       }

       /**
        * Get the row ID of the current buffer's current record, if any.  P2J
        * implementation of the ROWID function.
        *
        * @return  Row ID of the underlying data record.  If no record currently
        *          backs the buffer associated with <code>dmo</code>, a
        *          <code>rowid</code> initialized as unknown value is returned.
        */
       public rowid rowID()
       {
          return buffer().rowID();
       }

       /**
        * Get the row ID of the current buffer's current record, if any.
        *
        * @return  Row ID of the underlying data record.  If no record currently backs the buffer associated with
        *          <code>dmo</code>, a <code>null</code> value is returned.
        */
       public Long _rowID()
       {
          return buffer()._rowID();
       }

       /**
        * Indicate whether a backing data record currently is available.
        *
        * @return  <code>true</code> if data is available, <code>false</code> if
        *          no record backs this buffer.
        */
       public logical available()
       {
          return (logical.of(_available()));
       }

       /**
        * Indicate whether a backing data record currently is available.
        *
        * @return  <code>true</code> if data is available, <code>false</code> if
        *          no record backs this buffer.
        */
       public boolean _available()
       {
          return buffer().isAvailable();
       }

       /**
        * Checks if the record in the buffer has changed in database since the
        * last <code>FIND CURRENT</code> or <code>GET CURRENT</code>.
        * P2J implementation of 4GL function <code>CURRENT-CHANGED</code>.
        *
        * @return  <code>true</code> if the database record differs from the
        *          value in memory buffer that was queried with the last
        *          <code>FIND</code> and <code>false</code> otherwise
        */
       @LegacyAttribute(name = "CURRENT-CHANGED")
       public logical currentChanged()
       {
          return logical.of(_currentChanged());
       }

       /**
        * Checks if the record in the buffer has changed in database since the
        * last <code>FIND CURRENT</code> or <code>GET CURRENT</code>.
        * P2J implementation of 4GL function <code>CURRENT-CHANGED</code>.
        *
        * @return  <code>true</code> if the database record differs from the
        *          value in memory buffer that was queried with the last
        *          <code>FIND</code> and <code>false</code> otherwise
        */
       public boolean _currentChanged()
       {
          return buffer().isRecordChanged();
       }

       /**
        * Indicate whether the buffer associated currently contains a record, and
        * whether that record is transient.  A record is transient if it has been
        * newly created and has not yet been saved to the database.
        *
        * @return  <code>true</code> if a record exists and is new, else
        *          <code>false</code>.
        */
       public logical newlyCreated()
       {
          return (logical.of(_newlyCreated()));
       }

       /**
        * Indicate whether the buffer associated currently contains a record, and
        * whether that record is transient.  A record is transient if it has been
        * newly created and has not yet been saved to the database.
        *
        * @return  <code>true</code> if a record exists and is new, else
        *          <code>false</code>.
        */
       public boolean _newlyCreated()
       {
          return buffer().isNewlyCreated();
       }

       /**
        * Indicate whether the last record retrieval attempt was denied because
        * the target record was locked.
        *
        * @return  <code>true</code> if the record was locked;  <code>false</code>
        *          if the attempt succeeded, or if this query has not yet
        *          attempted to retrieve a record.
        */
       public logical locked()
       {
          return (logical.of(_locked()));
       }

       /**
        * Indicate whether the last record retrieval attempt was denied because
        * the target record was locked.
        *
        * @return  <code>true</code> if the record was locked;  <code>false</code>
        *          if the attempt succeeded, or if this query has not yet
        *          attempted to retrieve a record.
        */
       public boolean _locked()
       {
          return buffer().wasLocked();
       }

       /**
        * Indicate whether the last record retrieval attempt failed because the
        * query criteria was ambiguous.
        *
        * @return  <code>true</code> if the query was ambiguous;
        *          <code>false</code> if the attempt succeeded, or if no attempt
        *          has yet been made.
        */
       public logical ambiguous()
       {
          return (logical.of(_ambiguous()));
       }

       /**
        * Indicate whether the last record retrieval attempt failed because the
        * query criteria was ambiguous.
        *
        * @return  <code>true</code> if the query was ambiguous;
        *          <code>false</code> if the attempt succeeded, or if no attempt
        *          has yet been made.
        */
       public boolean _ambiguous()
       {
          return buffer().wasAmbiguous();
       }

       /**
        * Getter for the IS-MULTI-TENANT attribute.
        *
        * @return   See above.
        */
       @Override
       public logical isMultiTenant()
       {
          return logical.of(buffer().getDmoInfo().multiTenant);
       }

       /**
        * Setter for the IS-MULTI-TENANT attribute.
        *
        * @param    l
        *           The flag value.
        */
       @Override
       public void setMultiTenant(logical l)
       {
          ErrorManager.recordOrShowError(4052, "IS-MULTI-TENANT", "setable" /*sic!*/, LegacyResource.BUFFER);
       }

       /**
        * Setter for the IS-MULTI-TENANT attribute.
        *
        * @param    l
        *           The flag value.
        */
       @Override
       public void setMultiTenant(boolean l)
       {
          ErrorManager.recordOrShowError(4052, "IS-MULTI-TENANT", "setable" /*sic!*/, LegacyResource.BUFFER);
       }

       /**
        * Perform a RAW-TRANSFER operation from this buffer to the given raw variable. Serializes the
        * record currently in the buffer into the specified field.
        *
        * @param   r
        *          A <code>raw</code> field to which to copy the record in this buffer.
        */
       public void rawCopyTo(raw r)
       {
          // TODO: this is just a basic implementation, it needs improvements to handle boundary
          //       conditions, add proper error handling is missing and the serialization is
          //       not binary compatible with the 4GL version

          if (r == null)
          {
             return;
          }

          boolean error = false;

          // obtain the signature for the buffer
          ArrayList<TableMapper.LegacyFieldInfo> fields = TableMapper.getLegacyOrderedList(this, true);
          TableSignature sig = new TableSignature(fields);

          // collect all serialized output in an in-memory byte[]
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream    oos = null;

          try
          {
             // collect all serialized output in an in-memory byte[]
             oos = new ObjectOutputStream(baos);

             // serialize the signature
             sig.writeExternal(oos);

             RecordBuffer         buffer  = buffer();
             BufferReference      proxy   = buffer.getDMOProxy();
             Map<String, Method>  getters = buffer.getDmoInfo().getLegacyGetterMap();
             Map<String, Integer> extents = buffer.getDmoInfo().getExtentMap();

             // iterate through the field descriptors (in POSITION sequence)
             for (int j = 0; j < fields.size(); j++)
             {
                TableMapper.LegacyFieldInfo lfi = fields.get(j);
                String prop = lfi.getJavaName();

                // extent or not?
                Integer extent = extents.get(prop);

                // obtain the getter methods for the current property
                Method getter = getters.get(prop);

                Externalizable result;

                // read the value
                if (extent == null)
                {
                   // scalar case
                   result = (Externalizable) getter.invoke(proxy);

                   // serialize
                   result.writeExternal(oos);
                }
                else
                {
                   // array case
                   for (int i = 0; i < extent; i++)
                   {
                      result = (Externalizable) getter.invoke(proxy, i);

                      // serialize
                      result.writeExternal(oos);
                   }
                }
             }
          }

          catch (IOException | SecurityException | IllegalAccessException | InvocationTargetException ex)
          {
             error = true;
             ErrorManager.recordOrThrowError(new NumberedException("RAW-TRANSFER failed!", -1, ex));
          }

          finally
          {
             // the baos does not need to be closed and can be accessed even after you call close()
             if (oos != null)
             {
                try
                {
                   // this one should be closed
                   oos.close();
                }

                catch (IOException ex)
                {
                   // don't care
                }
             }
          }

          // save the result in the raw variable
          if (!error)
          {
             r.assign(baos.toByteArray());
          }
       }

       /**
        * Perform a RAW-TRANSFER operation from this buffer to the given field, which should be of
        * type <code>raw</code>. Serializes the record currently in the buffer into the specified
        * field.
        *
        * @param   field
        *          A <code>raw</code> field to which to copy the record in this buffer.
        */
       public void rawCopyTo(FieldReference field)
       {
          if (field == null)
          {
             return;
          }

          raw r = new raw();

          rawCopyTo(r);
          field.set(r);
       }

       /**
        * Perform a RAW-TRANSFER operation from this buffer to the given buffer.
        *
        * @param   buffer
        *          A buffer into which to copy the record in this buffer.
        * @return <code>true</code> if successful
        */
       public boolean rawCopyTo(Buffer buffer)
       {
          if (buffer == null)
          {
             return false;
          }

          raw r = new raw();

          rawCopyTo(r);
          return buffer.rawCopyFrom(r);
       }

       /**
        * Perform a RAW-TRANSFER operation from the given raw field into this buffer. Deserializes the
        * data in the specified field into this buffer.
        *
        * @param   field
        *          A <code>raw</code> field from which to copy the record into this buffer.
        */
       public void rawCopyFrom(FieldReference field)
       {
          if (field == null)
          {
             return;
          }

          rawCopyFrom((raw) field.get());
       }

       /**
        * Perform a RAW-TRANSFER operation from the given raw field into this buffer. Deserializes the
        * data in the specified field into this buffer.
        *
        * @param   field
        *          A <code>raw</code> field from which to copy the record into this buffer.
        * @return <code>true</code> if successful
        */
       public boolean rawCopyFrom(raw field)
       {
          // TODO: this is just a basic implementation, it needs improvements to handle boundary
          //       conditions, add proper error handling is missing and the deserialization is
          //       not binary compatible with the 4GL version

          if (field == null || field.isUnknown())
          {
             ErrorManager.recordOrShowError(
                   4952,
                   "Couldn't extract raw value from source field of RAW-TRANSFER",
                   false, false, true
             );
             return true;
          }

          byte[] data = field.getByteArray();

          if (data == null || data.length == 0)
          {
             ErrorManager.recordOrShowError(
                   4952,
                   "Couldn't extract raw value from source field of RAW-TRANSFER",
                   false, false, true
             );
             return true;
          }

          // this should create a new record (causing any existing record release and firing triggers as needed
          if (!_available())
          {
             create();
          }

          // obtain the signature for the buffer
          ArrayList<TableMapper.LegacyFieldInfo> fields = TableMapper.getLegacyOrderedList(this, true);
          TableSignature tarSig = new TableSignature(fields);

          // collect all serialized output in an in-memory byte[]
          ByteArrayInputStream bais = new ByteArrayInputStream(data);
          try (ObjectInputStream ois = new ObjectInputStream(bais))
          {
             // read all serialized input from an in-memory byte[]

             TableSignature srcSig = new TableSignature();

             // deserialize the signature
             srcSig.readExternal(ois);

             if (!srcSig.equals(tarSig))
             {
                ErrorManager.recordOrShowError(
                      4955,
                      "Table signatures do not match in RAW-TRANSFER operation",
                      false, false, true
                );
                return true;
             }

             RecordBuffer         buffer  = buffer();
             Map<String, Integer> extents = buffer.getDmoInfo().getExtentMap();
             Map<String, BaseDataType[]> values = readValues(fields, extents, ois);
             if (values == null)
             {
                return true;
             }

             BufferReference      proxy   = buffer.getDMOProxy();
             Map<String, Method>  setters = buffer.getDmoInfo().getLegacySetterMap();

             boolean batchError = true;
             RecordBuffer.startBatch(buffer.bufferManager, true);

             try
             {
                // iterate through the field descriptors (in POSITION sequence)
                for (int j = 0; j < fields.size(); j++)
                {
                   TableMapper.LegacyFieldInfo lfi = fields.get(j);
                   String prop = lfi.getJavaName();
                   BaseDataType[] pvals = values.get(prop);

                   // extent or not?
                   Integer extent = extents.get(prop);

                   // obtain the setter methods for the current property
                   Method setter = setters.get(prop);

                   BaseDataType value = null;

                   // process the value
                   if (extent == null)
                   {
                      // call the default constructor
                      value = pvals[0];

                      // scalar case
                      setter.invoke(proxy, value);
                   }
                   else
                   {
                      // array case
                      for (int i = 0; i < extent; i++)
                      {
                         // call the default constructor
                         value = pvals[i];

                         // scalar case
                         setter.invoke(proxy, i, value);
                      }
                   }
                }

                batchError = false;
             }
             finally
             {
                RecordBuffer.endBatch(buffer.bufferManager, batchError);
             }
             return true;
          }
          catch (StreamCorruptedException sce)
          {
             ErrorManager.recordOrShowError(
                   4952,
                   "Couldn't extract raw value from source field of RAW-TRANSFER",
                   false, false, true
             );
             return true;
          }
          catch (IOException                  |
                 ReflectiveOperationException |
                 SecurityException            |
                 IllegalArgumentException e)

          {
             // TODO: if something happened during the copy but after the create, do we need to put
             //       the original record back in?

             ErrorManager.recordOrThrowError(new NumberedException("RAW-TRANSFER failed!", -1, e));
          }
          return false;
       }

       /**
        * RAW-TRANSFER() method implementation that maps its processing to the same methods used
        * for implementation of the RAW-TRANSFER statement.  This will serialize data from this
        * buffer into a RAW field or buffer OR it will deserialize data from a RAW field or buffer
        * into this buffer.
        *
        * @param   toMode
        *          {@code true} if the copy direction is into the handle's resource and {@code false}
        *          if the data is being copied into the buffer.
        * @param   h
        *          A buffer or raw buffer field.
        *
        * @return  <code>true</code> if RAW-TRANSFER() succeeds, else <code>false</code>.
        */
       @LegacyMethod(name = "RAW-TRANSFER")
       public logical rawTransfer(boolean toMode, handle h)
       {
          if (h == null || h.isUnknown())
          {
             ErrorManager.recordOrShowError(
                   4065,
                   "**The RAW-TRANSFER attribute on the BUFFER widget has invalid arguments",
                   false, false, false
             );
             return logical.FALSE;
          }

          WrappedResource wr = (WrappedResource) h.get();

          if (wr instanceof Buffer)
          {
             Buffer buf = (Buffer) wr;

             if (toMode)
             {
                if (!available().booleanValue())
                {
                   ErrorManager.recordOrShowError(
                         4951,
                         "Source element of a RAW-TRANSFER statement has no record",
                         false, false, true
                   );
                   return logical.FALSE;
                }
                return logical.of(rawCopyTo(buf));
             }
             else
             {
                if (!buf.available().booleanValue())
                {
                   ErrorManager.recordOrShowError(
                         4951,
                         "Source element of a RAW-TRANSFER statement has no record",
                         false, false, true
                   );
                   return logical.FALSE;
                }
                return logical.of(buf.rawCopyTo(this));
             }
          }
          else if (wr instanceof BufferField)
          {
             BufferField fld = (BufferField) wr;
             String ftype = ((CommonField) h.getResource()).getDataType().getValue();
             if (!"raw".equals(ftype))
             {
                String fn = h.unwrap().name().getValue();
                ErrorManager.recordOrShowDatabaseError(
                      4824,
                      String.format("Field '%s' referenced in RAW-TRANSFER must be of type RAW", fn),
                      false, false
                );
                return logical.FALSE;
             }
             if (toMode)
             {
                if (!available().booleanValue())
                {
                   ErrorManager.recordOrShowError(
                         4951,
                         "Source element of a RAW-TRANSFER statement has no record",
                         false, false, true
                   );
                   return logical.FALSE;
                }
                raw r = new raw();
                rawCopyTo(r);

                fld.changeValue(r);
             }
             else
             {
                if (!h.unwrapBufferField().available().booleanValue())
                {
                   ErrorManager.recordOrShowError(
                         4951,
                         "Source element of a RAW-TRANSFER statement has no record",
                         false, false, true
                   );
                   return logical.FALSE;
                }
                BaseDataType bdt = fld.value();

    //            if (!(bdt instanceof raw))
    //            {
    //               // TODO: error?
    //               return logical.FALSE;
    //            }

                return logical.of(rawCopyFrom((raw) bdt));
             }
          }
          else
          {
             // untested at this time for 'database behavior' related to throwing structured exceptions
             // - see recordOrShowDatabaseError
             ErrorManager.recordOrShowError(
                   4067,
                   "The second argument for RAW-TRANSFER on the BUFFER widget should" +
                   " not be of type WIDGET-HANDLE",
                   false, true, false
             );
             return logical.FALSE;
          }

          // TODO: we probably need to know if there was a failure in the backing worker methods
          //       but the current signatures don't return a value
          return logical.TRUE;
       }

       /**
        * RAW-TRANSFER() method implementation that maps its processing to the same methods used
        * for implementation of the RAW-TRANSFER statement.  This will serialize data from this
        * buffer into a RAW field or buffer OR it will deserialize data from a RAW field or buffer
        * into this buffer.
        *
        * @param   toMode
        *          {@code true} if the copy direction is into the handle's resource and {@code false}
        *          if the data is being copied into the buffer.
        * @param   h
        *          A buffer or raw buffer field.
        *
        * @return  <code>true</code> if RAW-TRANSFER() succeeds, else <code>false</code>.
        */
       @LegacyMethod(name = "RAW-TRANSFER")
       public logical rawTransfer(logical toMode, handle h)
       {
          if (toMode == null || toMode.isUnknown())
          {
             // TODO: check this behavior
             return logical.FALSE;
          }

          return rawTransfer(toMode.booleanValue(), h);
       }

       /**
        * Create a new record in the buffer.
        *
        * @throws  ErrorConditionException
        *          if there is an error retrieving the next available primary key
        *          from the database or locking the record.
        */
       public void create()
       {
          buffer().create();
       }

       /**
        * Delete the record currently in the buffer, if any.
        *
        * @return  {@code true} if the delete was possible.
        *
        * @throws  ErrorConditionException
        *          if no record currently is loaded in the buffer, or if there is
        *          an error deleting the current record from the database.
        */
       public logical deleteRecord()
       throws ErrorConditionException
       {
          if (isBeforeBuffer() && !setUpBeforeBuffer)
          {
             // the BEFORE-BUFFERS can only be queried, not altered in any way (REMOVE/CHANGE)
             ErrorManager.recordOrThrowError(12374, doGetName(), "");
             // You may not delete BEFORE-TABLE <name> record.  Use ACCEPT-ROW-CHANGES instead.
             return logical.FALSE;
          }

          // no validation is taken in consideration
          return deleteRecord(() -> logical.TRUE, () -> new character(""));
       }

       /**
        * Delete the record currently in the buffer, IF and ONLY IF the given validation expression
        * evaluates <code>true</code>.
        *
        * @param    valexp
        *           The validation expression to evaluate.
        * @param    valmsg
        *           The error message to display when validation fails.
        *
        * @return   <code>true</code> if validation succeeds, <code>false</code> if it fails and no
        *           exception is raised.
        *
        * @throws   ErrorConditionException
        *           If no record currently is loaded in the buffer, or if there is an error deleting
        *           the current record from the database or if the validation expression fails.
        */
       public logical deleteRecord(Supplier<logical> valexp, String valmsg)
       throws ErrorConditionException
       {
          return deleteRecord(valexp, () -> new character(valmsg));
       }

       /**
        * Delete the record currently in the buffer, IF and ONLY IF the given validation expression
        * evaluates <code>true</code>.
        *
        * @param    valexp
        *           The validation expression to evaluate.
        * @param    valmsg
        *           The error message to display when validation fails.
        *
        * @return   <code>true</code> if validation succeeds, <code>false</code> if it fails and no
        *           exception is raised.
        *
        * @throws   ErrorConditionException
        *           If no record currently is loaded in the buffer, or if there is an error deleting
        *           the current record from the database or if the validation expression fails.
        */
       public logical deleteRecord(Supplier<logical> valexp, Supplier<character> valmsg)
       throws ErrorConditionException
       {
          RecordBuffer buffer = buffer();

          if (!buffer.isTemporary())
          {
             if (!verifyTransaction(buffer))
             {
                return logical.FALSE;
             }

             // the validation is done at compile type, thus dynamic buffers with validation
             // can't be deleted - also no delete trigger is fired
             if (buffer.isDynamic())
             {
                Class<? extends DataModelObject> iface = buffer.getDMOInterface();
                if (TableMapper.hasValidation(iface))
                {
                   String msg = "Table " + buffer.getDmoInfo().legacyTable + " is defined with schema delete " +
                                "validation and may only be deleted by the DELETE statement";
                   ErrorManager.recordOrShowError(7347, msg, false, false);

                   return logical.FALSE;
                }
             }
          }

          if (dataSet != null) // implies buffer.isTemporary()
          {
             // checks whether tracking is active
             if (buffer.getParentTable()._isTrackingChanges())
             {
                if (!invokeCallback(ROW_DELETE_EVENT))
                {
                   returnError(); // veto on record delete
                }
             }
          }

          try
          {
             return logical.of(buffer.delete(valexp, valmsg));
          }
          catch (PersistenceException e)
          {
             return logical.FALSE;
          }
       }

       /**
        * Delete all records associated with this buffer, which match the criteria specified by the
        * given where clause.  Note that this where clause must not contain DMO alias qualifiers.
        * <p>
        * <b>NOTE:</b>  the current implementation must not be invoked for a DMO which is backed by a
        * permanent table;  only temp-table bulk delete is supported at this time.
        * <p>
        * When this method returns:
        * <ul>
        *    <li><strong>temp-table</strong>buffers became empty (Ie. {@code available} returns
        *          {@code false}). This is because these buffers are used in a loop and the last
        *          record is also deleted. P2J will detect these loops and convert it to the much
        *          faster bulk delete. P4GL does not allow bulk delete using SQL
        *          {@code DELETE FROM ... WHERE} syntax.
        *    <li>P2J does not support yet bulk deletion of <strong>permanent table</strong> buffers.
        *          In P4GL they will be empty if the predicate matches the currently stored record and
        *          remains unaffected otherwise.
        * </ul>
        *
        * @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 HQL 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  ErrorConditionException
        *          if there is an error deleting the records.
        * @throws  UnsupportedOperationException
        *          if invoked on a buffer associated with a permanent table.
        */
       public void delete(DataModelObject[] suppDMOs, String where, Object... args)
       {
          RecordBuffer buffer = buffer();
          AbstractQuery.preprocessSubstitutionArguments(buffer.bufferManager, () -> false, false, args);
          try
          {
             buffer.delete(suppDMOs, where, args);
          }
          catch (PersistenceException exc)
          {
             ErrorManager.recordOrThrowError(exc);
          }
       }

       /**
        * Delete all records associated with this buffer, which match the criteria specified by the
        * given where clause.  Note that this where clause must not contain DMO alias qualifiers.
        * <p>
        * <b>NOTE:</b>  the current implementation must not be invoked for a DMO which is backed by a
        * permanent table;  only temp-table bulk delete is supported at this time.
        * <p>
        * When this method returns:
        * <ul>
        *    <li><strong>temp-table</strong>buffers became empty (Ie. {@code available} returns
        *          {@code false}). This is because these buffers are used in a loop and the last
        *          record is also deleted. P2J will detect these loops and convert it to the much
        *          faster bulk delete. P4GL does not allow bulk delete using SQL
        *          {@code DELETE FROM ... WHERE} syntax.
        *    <li>P2J does not support yet bulk deletion of <strong>permanent table</strong> buffers.
        *          In P4GL they will be empty if the predicate matches the currently stored record and
        *          remains unaffected otherwise.
        * </ul>
        *
        * @param   where
        *          An HQL 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  ErrorConditionException
        *          if there is an error deleting the records.
        * @throws  UnsupportedOperationException
        *          if invoked on a buffer associated with a permanent table.
        */
       public void delete(String where, Object ...args)
       {
          delete(null, where, args);
       }

       /**
        * Bulk delete all records associated with the {@code Buffer} or {@code TempTable}.  This allows bulk
        * delete when TRACKING-CHANGES is on, as this emulates a {@code FOR EACH tt1. DELETE tt1. end.} block.
        *
        * @throws  ErrorConditionException
        *          If an error occurs performing the bulk delete operation.
        */
       @Override
       public void deleteForAll()
       {
          if (!(this instanceof Temporary))
          {
             ErrorManager.recordOrShowError(7352);
             // You may only invoke EMPTY-TEMP-TABLE on valid temp-tables.
             return;
          }

          // need to update the ORIGIN-HANDLE attribute. Will defer execution through default buffer
          TempTableBuilder.asTempTable((Temporary) this).deleteForAll();
       }

       /**
        * Bulk delete all records associated with the buffer.
        * <p>
        * When this method returns, the buffer is empty (Ie. {@link #available} returns {@code false})
        * for both temp and permanent tables.
        * TODO: when is this invoked for permanent tables ?????
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the bulk delete operation.
        */
       @Override
       public logical deleteAll()
       {
          if (!(this instanceof Temporary))
          {
             ErrorManager.recordOrShowError(7352);
             // You may only invoke EMPTY-TEMP-TABLE on valid temp-tables.
             return logical.FALSE;
          }

          TempTable tt = TempTableBuilder.asTempTable((Temporary) this);
          if (tt == null)
          {
             LOG.log(Level.SEVERE,
                     "Nothing to delete, this dynamic TEMP-TABLE was not prepared yet.",
                     new Throwable());
             return logical.FALSE;
          }

          // need to update the ORIGIN-HANDLE attribute. Will defer execution through default buffer
          return tt.deleteAll();
       }

       /**
        * Aggregate method which averages the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void average(String column, NumberType num)
       {
          average(column, num, null);
       }

       /**
        * Aggregate method which averages the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void average(String column, NumberType num, String where)
       {
          average(column, num, where, (Object[]) null);
       }

       /**
        * Aggregate method which averages the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        * @param   args
        *          Query substitution arguments.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void average(String column, NumberType num, String where, Object...args)
       {
          buffer().average(column, num, where, args);
       }

       /**
        * Aggregate method which counts the rows in the table backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate. May be the wildcard
        *          character ("*").
        * @param   num
        *          Variable into which to store the aggregate result.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void count(String column, NumberType num)
       {
          count(column, num, null);
       }

       /**
        * Aggregate method which counts the rows in the table backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate. May be the wildcard
        *          character ("*").
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void count(String column, NumberType num, String where)
       {
          count(column, num, where, (Object[]) null);
       }

       /**
        * Aggregate method which counts the rows in the table backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate. May be the wildcard
        *          character ("*").
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        * @param   args
        *          Query substitution arguments.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void count(String column, NumberType num, String where, Object...args)
       {
          buffer().count(column, num, where, args);
       }

       /**
        * Aggregate method which finds the minimum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void minimum(String column, NumberType num)
       {
          minimum(column, num, null);
       }

       /**
        * Aggregate method which finds the minimum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void minimum(String column, NumberType num, String where)
       {
          minimum(column, num, where, (Object[]) null);
       }

       /**
        * Aggregate method which finds the minimum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        * @param   args
        *          Query substitution arguments.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void minimum(String column, NumberType num, String where, Object...args)
       {
          buffer().minimum(column, num, where, args);
       }

       /**
        * Aggregate method which finds the maximum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void maximum(String column, NumberType num)
       {
          maximum(column, num, null);
       }

       /**
        * Aggregate method which finds the maximum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void maximum(String column, NumberType num, String where)
       {
          maximum(column, num, where, (Object[]) null);
       }

       /**
        * Aggregate method which finds the maximum value of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        * @param   args
        *          Query substitution arguments.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void maximum(String column, NumberType num, String where, Object...args)
       {
          buffer().maximum(column, num, where, args);
       }

       /**
        * Aggregate method which sums the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void sum(String column, NumberType num)
       {
          sum(column, num, null);
       }

       /**
        * Aggregate method which sums the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void sum(String column, NumberType num, String where)
       {
          sum(column, num, where, (Object[]) null);
       }

       /**
        * Aggregate method which sums the values of a single column across rows in the table
        * backing this buffer.
        *
        * @param   column
        *          An expression indicating the name of the column to aggregate.
        * @param   num
        *          Variable into which to store the aggregate result.
        * @param   where
        *          HQL where clause used to filter the records.
        * @param   args
        *          Query substitution arguments.
        *
        * @throws  ErrorConditionException
        *          if an error occurs performing the aggregate operation.
        */
       public void sum(String column, NumberType num, String where, Object...args)
       {
          buffer().sum(column, num, where, args);
       }

       /**
        * Release the record currently in the buffer, if any. No validation, no write trigger is fired. This
        * method is invoked as result of an internal error condition when the content of the buffer failed
        * internal validation and the buffer must be released.
        */
       public void drop()
       {
          buffer().release(true, false);
       }

       /**
        * Validate and release the record currently in the buffer, if any. May cause a write trigger to fire.
        *
        * @throws  ErrorConditionException
        *          if the buffer fails validation.
        */
       @Override
       public void release()
       {
          // the converted RELEASE statement is forcefully flushing the record, so the fact that the underlying
          // record is loaded in multiple buffers in the same time shouldn't be honored.
          release(false);
       }

       /**
        * Validate and release the record currently in the buffer, if any. May cause a write trigger to fire.
        *
        * @param   honorMultipleReferences
        *          {@code true} if the flushing of the existing record should be suppressed if the record is also
        *          loaded in other buffers.
        *
        * @throws  ErrorConditionException
        *          if the buffer fails validation.
        */
       public void release(boolean honorMultipleReferences)
       {
          buffer().release(false, true, honorMultipleReferences);
       }

       /**
        * Validate the record currently in the buffer, if any.
        *
        * @throws  ErrorConditionException
        *          if the buffer fails validation.
        */
       @Override
       public void validate()
       {
          try
          {
             buffer().validate(true);
          }
          catch (ValidationException e)
          {
             // rethrow as 4GL error condition
             ErrorManager.recordOrThrowError(e.getNumber(), e.getMessage(), false, false);
          }
       }

       /**
        * Validate the record currently in the buffer, if any.
        *
        * @throws  ErrorConditionException
        *          if the buffer fails validation.
        */
       @Override
       public logical bufferValidate()
       {
          RecordBuffer recBuf = buffer();

          try
          {
             recBuf.validate(true);
             return logical.TRUE;
          }
          catch (ValidationException e)
          {
             if (recBuf.isNewlyCreated())
             {
                // untested at this time for 'database behavior' related to throwing structured exceptions
                // - see recordOrShowDatabaseError
                ErrorManager.recordOrShowError(e.getNumber(), e.getMessage(), false, false, false);
             }
          }

          return logical.FALSE;
       }

       /**
        * Conversion of the RECORD-LENGTH built-in function.
        *
        * @return  Length of the record currently in the buffer, in bytes.
        */
       @Override
       public integer recordLength()
       {
          if (!_available())
          {
             return new integer(); // quick out
          }

          RecordBuffer         buffer  = buffer();
          BufferReference      proxy   = buffer.getDMOProxy();
          Map<String, Method>  getters = buffer.getDmoInfo().getLegacyGetterMap();
          Map<String, Integer> extents = buffer.getDmoInfo().getExtentMap();

          int counter = 15; // hidden 2 fields and any header
          int fieldCounter = getters.size() + 2;
          if (fieldCounter > 15)
          {
             counter += 3;
          }
          counter += 2 * ((fieldCounter + 1) / 16);

          for (Map.Entry<String, Method> entry : getters.entrySet())
          {
             String prop = entry.getKey();
             Method method = entry.getValue();
             Integer ext = extents.get(prop);
             if (ext == null)
             {
                try
                {
                   BaseDataType bdt = (BaseDataType) method.invoke(proxy);
                   counter += bdt.getSize();
                }
                catch (IllegalAccessException | InvocationTargetException e)
                {
                   // log it?
                }
             }
             else
             {
                counter += 4;  // probably one 32bit int = the size of the extent
                if (ext > 15)
                {
                   counter += 3;
                }
                counter += 2 * ((ext + 1) / 16);

                for (int k = 0; k < ext; k++)
                {
                   try
                   {
                      BaseDataType bdt = (BaseDataType) method.invoke(proxy, k);
                      counter += bdt.getSize();
                   }
                   catch (IllegalAccessException | InvocationTargetException e)
                   {
                      // log it ?
                   }
                }
             }
          }

          return new integer(counter);
       }

       /**
        * Resolve the BUFFER-FIELD with the given converted DMO property name.
        *
        * @param     propertyName
        *            The property name.
        *
        * @return    See above.
        */
       public BufferFieldImpl bufferFieldForProperty(String propertyName)
       {
          if (fieldsByName == null)
          {
             fieldsByName = new HashMap<>();
          }

          BufferFieldImpl field = fieldsByName.get(propertyName);

          if (field == null)
          {
             RecordBuffer recBuf = buffer();
             int fieldId = TableMapper.getLegacyFieldInfo(recBuf, propertyName).getFieldId();

             field = new BufferFieldImpl(_instantiatingProcedure(), (DataModelObject) this, propertyName);

             if (fields == null)
             {
                int numFields = _numFields();
                fields = new BufferFieldImpl[numFields + 1];
             }

             fields[fieldId] = field;
             fieldsByName.put(propertyName, field);
          }

          return field;
       }

       /**
        * Conversion of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular buffer of a query or DataSet object.
        *
        * @param   fieldNumber
        *          field number. An INTEGER expression representing the sequence
        *          number of the field in the buffer.
        *
        * @return  handle to a particular field in the buffer.
        */
       @Override
       public handle bufferField(int fieldNumber)
       {
          if (fieldHandles == null)
          {
             fieldHandles = new handle[_numFields() + 1];
          }

          if (fieldNumber <= 0 || fieldNumber > _numFields())
          {
             String msg = "BUFFER-FIELD sequence number must be from 1 to " +
                _numFields() + " for buffer " + buffer().getDmoInfo().legacyTable;
             ErrorManager.recordOrShowDatabaseError(7350, msg, false, false);

             return new handle();
          }

          handle hf = fieldHandles[fieldNumber];
          if (hf == null)
          {
             BufferField field = bufferFieldNative(fieldNumber);
             if (field != null)
             {
                hf = new handle(field);
                fieldHandles[fieldNumber] = hf;
             }
             else
             {
                hf = new handle();
             }
          }

          return hf;
       }

       /**
        * Conversion of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular buffer of a query or DataSet object.
        *
        * @param   fieldNumber
        *          field number. An INTEGER expression representing the sequence
        *          number of the field in the buffer.
        *
        * @return  a particular field in the buffer.
        */
       public BufferFieldImpl bufferFieldNative(int fieldNumber)
       {
          int fieldCount = _numFields();
          RecordBuffer recBuf = buffer();
          String legacyTableName = recBuf.getDmoInfo().legacyTable;
          if (fieldNumber <= 0 || fieldNumber > fieldCount)
          {
             String msg = "BUFFER-FIELD sequence number must be from 1 to " +
                          fieldCount + " for buffer " + legacyTableName;
             ErrorManager.recordOrShowDatabaseError(7350, msg, false, false);

             return null;
          }

          BufferFieldImpl field = fields == null ? null : fields[fieldNumber];
          if (field == null)
          {
             String hibernateName;
             Property fieldProperty = recBuf.getDmoInfo().getExistingFieldProperty(fieldNumber);
             if (fieldProperty.original == null || fieldProperty.original.isEmpty())
             {
                hibernateName = fieldProperty.name;
             }
             else
             {
                hibernateName = fieldProperty.original;
             }

             field = new BufferFieldImpl(_instantiatingProcedure(), (DataModelObject) this, hibernateName);

             if (fields == null)
             {
                int numFields = _numFields();
                fields = new BufferFieldImpl[numFields + 1];
             }
             fields[fieldNumber] = field;
             if (fieldsByName == null)
             {
                fieldsByName = new HashMap<>();
             }
             fieldsByName.put(field.getOriginalPropertyName(), field);
          }

          return field;
       }

       /**
        * Implementation of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular field within a buffer.
        *
        * @param   fieldName
        *          The legacy field name. A CHARACTER string expression representing the name
        *          of the field in the buffer.
        *
        * @return  handle to a particular field in the buffer.
        */
       @Override
       public handle bufferField(String fieldName)
       {
          return new handle(bufferFieldNative(fieldName));
       }

       /**
        * Implementation of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets a particular field within a buffer.
        *
        * @param   fieldName
        *          The legacy field name. A CHARACTER string expression representing the name
        *          of the field in the buffer.
        *
        * @return  a particular field in the buffer.
        */
       public BufferField bufferFieldNative(String fieldName)
       {
          Property property = buffer().getDmoInfo().byLegacyName(fieldName);
          if (property != null)
          {
             BufferFieldImpl field = bufferFieldForProperty(property.name);
             if (field != null)
             {
                return field;
             }
          }

          String msg = "BUFFER-FIELD " + fieldName + " was not found in buffer " + doGetName();
          ErrorManager.recordOrShowDatabaseError(7351, msg, false, false);

          // return unknown handle
          return null;
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   buffer
        *          The source buffer to copy from.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(Buffer buffer)
       {
          return bufferCopy(buffer, null, null);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   buffer
        *          The source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(Buffer buffer, character except)
       {
          return bufferCopy(buffer, except, null);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   buffer
        *          The source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be copied.  Each pair must contain
        *          one field name from the source and one field name from the target.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(Buffer buffer, character except, character pairs)
       {
          return bufferCopy(buffer, except, pairs, logical.FALSE);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   buffer
        *          The source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be copied.  Each pair must contain
        *          one field name from the source and one field name from the target.
        * @param   noLobs
        *          If <code>TRUE</code>, BLOB and CLOB fields are ignored during the copy.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(Buffer buffer, character except, character pairs, logical noLobs)
       {
          buffer().setBufferCopyDestination(true);
          return RecordBuffer.copy(buffer(), ((BufferImpl) buffer).buffer(), except, pairs, noLobs);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   bufHandle
        *          The handle of the source buffer to copy from.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(handle bufHandle)
       {
          return bufferCopy((Buffer) bufHandle.get());
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   bufHandle
        *          The handle of the source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(handle bufHandle, character except)
       {
          return bufferCopy((Buffer) bufHandle.get(), except);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   bufHandle
        *          The handle of the source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be copied.  Each pair must contain
        *          one field name from the source and one field name from the target.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(handle bufHandle, character except, character pairs)
       {
          return bufferCopy((Buffer) bufHandle.get(), except, pairs);
       }

       /**
        * Conversion of BUFFER-COPY() method (KW_BUF_COPY).
        * <p>
        * This method copies any common fields, determined by name, data type,
        * and extent-matching, from the source buffer to the receiving buffer.
        * If there are fields in one buffer that do not exist in the other, they
        * are ignored. This method is used to accommodate temp-tables of joins.
        *
        * @param   bufHandle
        *          The handle of the source buffer to copy from.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the copy process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be copied.  Each pair must contain
        *          one field name from the source and one field name from the target.
        * @param   noLobs
        *          If <code>TRUE</code>, BLOB and CLOB fields are ignored during the copy.
        *
        * @return  <code>true</code> if BUFFER-COPY() succeeds, else <code>false</code>.
        */
       public logical bufferCopy(handle bufHandle,
                                 character except,
                                 character pairs,
                                 logical noLobs)
       {
          return bufferCopy((Buffer) bufHandle.get(), except, pairs, noLobs);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   buffer
        *          Buffer with which to compare.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(Buffer buffer)
       {
          return bufferCompare(buffer, null, null, null);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   buffer
        *          Buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(Buffer buffer, character mode)
       {
          return bufferCompare(buffer, mode, null, null);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   buffer
        *          Buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(Buffer buffer, character mode, character except)
       {
          return bufferCompare(buffer, mode, except, null);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   srcBuffer
        *          Buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be compared.  Each pair must
        *          contain one field name from the source and one field name from the target.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(Buffer srcBuffer,
                                    character mode,
                                    character except,
                                    character pairs)
       {
          return bufferCompare(srcBuffer, mode, except, pairs, null);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   srcBuffer
        *          Buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be compared.  Each pair must
        *          contain one field name from the source and one field name from the target.
        * @param   noLobs
        *          If {@code true}, BLOB and CLOB fields are ignored during the comparison. May be
        *          {@code false} or {@code null} to include such fields.
        *
        * @return  {@code true} if BUFFER-COMPARE() succeeds, else {@code false}.
        */
       public logical bufferCompare(Buffer    srcBuffer,
                                    character mode,
                                    character except,
                                    character pairs,
                                    logical   noLobs)
       {
          return RecordBuffer.compare(((BufferImpl) srcBuffer).buffer(),
                                      buffer(),
                                      BufferCompare.Mode.fromString(mode),
                                      except,
                                      pairs,
                                      noLobs);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   bufHandle
        *          Handle of buffer with which to compare.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(handle bufHandle)
       {
          return bufferCompare((Buffer) bufHandle.get());
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   bufHandle
        *          Handle of buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(handle bufHandle, character mode)
       {
          return bufferCompare((Buffer) bufHandle.get(), mode);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   bufHandle
        *          Handle of buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(handle bufHandle, character mode, character except)
       {
          return bufferCompare((Buffer) bufHandle.get(), mode, except);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   bufHandle
        *          Handle of buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be compared.  Each pair must
        *          contain one field name from the source and one field name from the target.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(handle bufHandle, character mode, character except, character pairs)
       {
          return bufferCompare((Buffer) bufHandle.get(), mode, except, pairs);
       }

       /**
        * Conversion of BUFFER-COMPARE() method (KW_BUF_COMP).
        * <p>
        * This method does a rough compare of any common fields, determined by
        * name, data type, and extent-matching, between the source buffer and the
        * target buffer. The resulting logical value is either TRUE or FALSE as
        * a whole. A single field that does not compare causes the entire buffer
        * to return FALSE. If there are fields in one buffer that do not exist in
        * the other, they are ignored.
        *
        * @param   bufHandle
        *          Handle of buffer with which to compare.
        * @param   mode
        *          The text "binary" or "case-sensitive", to provide that type of comparison.
        * @param   except
        *          A comma-separated list of fields that will be ignored in the compare process.
        * @param   pairs
        *          A comma-separated list of field-name pairs to be compared.  Each pair must
        *          contain one field name from the source and one field name from the target.
        * @param   noLobs
        *          If <code>TRUE</code>, BLOB and CLOB fields are ignored during the compare.
        *
        * @return  <code>true</code> if BUFFER-COMPARE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferCompare(handle    bufHandle,
                                    character mode,
                                    character except,
                                    character pairs,
                                    logical   noLobs)
       {
          return bufferCompare((Buffer) bufHandle.get(), mode, except, pairs, noLobs);
       }

       /**
        * Conversion of BUFFER-CREATE() method (KW_BUF_CREA).
        * <p>
        * Creates a record, sets fields to their default values, and moves a copy of the record into the buffer.
        *
        * @return  {@code true} if successful
        */
       public logical bufferCreate()
       {
          if (isBeforeBuffer() && !setUpBeforeBuffer)
          {
             // the BEFORE-BUFFERS can only be queried, not altered in any way (REMOVE/CHANGE)
             ErrorManager.recordOrThrowError(12373, doGetName(), "");
             // You may not delete BEFORE-TABLE <name> record.  Use ACCEPT-ROW-CHANGES instead.
             return logical.FALSE;
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() && !verifyTransaction(buffer))
          {
             return logical.FALSE;
          }

          return logical.of(buffer.create());
       }

       /**
        * Conversion of BUFFER-RELEASE() method (KW_BUF_REL).
        * <p>
        * Releases a record from a buffer object. The BUFFER-RELEASE method
        * corresponds to the RELEASE statement.
        *
        * @return  <code>true</code> if BUFFER-RELEASE() succeeds,
        *          else <code>false</code>.
        */
       public logical bufferRelease()
       {
          boolean success = true;
          try
          {
             release();
          }
          catch (ErrorConditionException exc)
          {
             success = false;
          }

          return logical.of(success);
       }

       /**
        * Emitted for <code>::</code> operator used as r-value in a dynamic extent assignment, as the conversion
        * rules doesn't know if the target field is extent or scalar.
        *
        * @param    memberName
        *           The name of the member to be extracted.
        *
        * @return   The value of the member (scalar or extent) or unknown (?) if there is no such member.
        */
       @Override
       public Object derefPoly(String memberName)
       {
          Property property = buffer().getDmoInfo().byLegacyName(memberName);

          if (property == null)
          {
             ErrorManager.recordOrShowError(7351, memberName, doGetName());
             // BUFFER-FIELD <field-name> was not found in buffer <buffer-name>.
             return new unknown();
          }

          if (property.extent == 0)
          {
             return dereference(memberName);
          }

          if (!buffer().isAvailable() && !buffer().isUnknownMode())
          {
             String msg1 = "No " + doGetName() + " record is available";
             ErrorManager.displayError(91, msg1, true);

             String msg2 = "Unable to extract BUFFER-VALUE for field " + memberName;
             ErrorManager.displayError(7366, msg2, false);

             return new unknown();
          }

          BaseDataType[] ret = new BaseDataType[property.extent];
          for (int idx = 0; idx < ret.length; idx++)
          {
             ret[idx] = bufferFieldForProperty(property.name).value(idx + 1);
          }
          return ret;
       }

       /**
        * Dereference a named member from this collection.
        *
        * @param   memberName
        *          The legacy name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public BaseDataType dereference(String memberName)
       {
          Property property = buffer().getDmoInfo().byLegacyName(memberName);

          if (property == null)
          {
             ErrorManager.recordOrShowError(7351, memberName, doGetName());
             // BUFFER-FIELD <field-name> was not found in buffer <buffer-name>.
             return new unknown();
          }

          if (property.extent != 0)
          {
             ErrorManager.recordOrThrowError(14905);
             // Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
             return new unknown();
          }

          if (!buffer().isAvailable() && !buffer().isUnknownMode())
          {
             String msg1 = "No " + doGetName() + " record is available";
             ErrorManager.recordOrShowDatabaseError(91, msg1, false, true);

             String msg2 = "Unable to extract BUFFER-VALUE for field " + memberName;
             ErrorManager.recordOrShowDatabaseError(7366, msg2, false, false);

             return new unknown();
          }

          BaseDataType dbt = bufferFieldForProperty(property.name).value();
          if (dbt instanceof character)
          {
             // quirk in 4GL: the character fields retrieved using dereference operator are always
             // case-insensitive (no matter the initial field property case sensitivity)
             ((character) dbt).setCaseSensitive(false);
          }
          return dbt;
       }

       /**
        * Dereference a named member from this collection. If this collection is a ProDataSet then
        * the returned value is a {@link handle} to a named buffer, if the collection is a buffer
        * the returned value is the field value with the respective name. In the case that this
        * collection does not have a named member with the name specified <code>memberName</code>
        * the unknown value is returned.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public BaseDataType dereference(NumberType index, String memberName)
       {
          Property property = buffer().getDmoInfo().byLegacyName(memberName);

          if (property == null)
          {
             ErrorManager.recordOrShowError(7351, memberName, doGetName());
             // BUFFER-FIELD <field-name> was not found in buffer <buffer-name>.
             return new unknown();
          }

          if (!buffer().isAvailable())
          {
             String msg1 = "No " + doGetName() + " record is available";
             ErrorManager.recordOrShowDatabaseError(91, msg1, false, true);

             String msg2 = "Unable to extract BUFFER-VALUE for field " + memberName;
             ErrorManager.recordOrShowDatabaseError(7366, msg2, false, false);

             return new unknown();
          }

          if (property.extent == 0)
          {
             return dereference(memberName);
          }

          if (index.isUnknown())
          {
             return new unknown();
          }

          long idx = index.longValue();
          if (idx == 0)
          {
             // no error is shown/raised
             return new unknown();
          }

          if (idx < 1 || idx > property.extent)
          {
             String msg1 = "Array subscript " + index + " is out of range";
             ErrorManager.displayError(26, msg1, true);

             String msg2 = "Unable to extract BUFFER-VALUE for field " + memberName;
             ErrorManager.displayError(7366, msg2, false);

             return new unknown();
          }

          return bufferFieldForProperty(property.name).value((int) idx);
       }

       /**
        * Dereference a named member from this collection. If this collection is a ProDataSet then
        * the returned value is a {@link handle} to a named buffer, if the collection is a buffer
        * the returned value is the field value with the respective name. In the case that this
        * collection does not have a named member with the name specified <code>memberName</code>
        * the unknown value is returned.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public BaseDataType dereference(long index, String memberName)
       {
          return dereference(new int64(index), memberName);
       }

       /**
        * Dereference a named member from this collection. If this collection is a DataSet then
        * the returned value is a {@link handle} to a named buffer, if the collection is a buffer
        * the returned value is the field value with the respective name. In the case that this
        * collection does not have a named member with the name specified {@code memberName}
        * the unknown value is returned.
        *
        * @param   type
        *          The expected type of the returned value.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public <T extends BaseDataType> T dereference(Class<T> type, String memberName)
       {
          BaseDataType val = dereference(memberName);
          if (type.isAssignableFrom(val.getClass()))
          {
             return (T) val;
          }

          String errMsg2 =
              "Lead attributes in a chained-attribute expression (a:b:c) must be type " +
              "HANDLE or a user-defined type and valid (not UNKNOWN)";
          ErrorManager.recordOrThrowError(10068, errMsg2, false, false);

          return (T) BaseDataType.generateUnknown(type);
       }

       /**
        * This overloaded method is used when the dereference is the left-side of an assignment.
        * The implementation will extract the member from the collection and assign it the new value.
        *
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(String memberName, Object value)
       {
          if (isBeforeBuffer() && !setUpBeforeBuffer)
          {
             ErrorManager.recordOrShowError(12375, doGetName());
             // You may not update BEFORE-TABLE <name> record.
             ErrorManager.recordOrShowError(142, doGetName());
             // ** Unable to update <filename> Field.
             ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
             ErrorManager.recordOrThrowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             return;
          }

          Property property = buffer().getDmoInfo().byLegacyName(memberName);
          if (property == null)
          {
             String errMsg  = "BUFFER-FIELD " + memberName + " was not found in buffer " + doGetName();
             ErrorManager.recordOrThrowError(7351, errMsg);

             return;
          }

          boolean bulk = value.getClass().isArray();
          if (bulk)
          {
             BufferFieldImpl field = new BufferFieldImpl(_instantiatingProcedure(),
                                                         (DataModelObject) this,
                                                         property.name,
                                                         bulk);
             try
             {
                field.changeValue(value);
             }
             finally
             {
                // force the delete, this instance is required for bulk assign of an extent field, and is not a
                // real BUFFER-FIELD resource
                field.forceDelete();
             }
          }
          else
          {
             BufferFieldImpl field = bufferFieldForProperty(property.name);
             field.changeValue(value);
          }
       }

       /**
        * This overloaded method is used when the dereference is the left-side of an assignment.
        * The implementation will extract the member from the collection and assign it the new value.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(NumberType index, String memberName, Object value)
       {
          if (isBeforeBuffer() && !setUpBeforeBuffer)
          {
             ErrorManager.recordOrShowError(12375, doGetName());
             // You may not update BEFORE-TABLE <name> record.
             ErrorManager.recordOrShowError(142, doGetName());
             // ** Unable to update <filename> Field.
             ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
             ErrorManager.recordOrThrowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             return;
          }

          Property property = buffer().getDmoInfo().byLegacyName(memberName);
          if (property == null)
          {
             String errMsg  = "BUFFER-FIELD " + memberName + " was not found in buffer " + doGetName();
             ErrorManager.recordOrThrowError(7351, errMsg);

             return;
          }

          if (index.isUnknown())
          {
             ErrorManager.recordOrShowError(7365, memberName, String.valueOf(property.extent));
             // <field> BUFFER-VALUE argument must be valid array index, less than or equal to field extent <extent>
             ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
             ErrorManager.recordOrThrowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             return;
          }

          long idx = index.longValue();
          if (property.extent == 0 && idx == 0)
          {
             dereference(memberName, value);
             return;
          }

          if (idx == 0)
          {
             ErrorManager.recordOrShowError(26, String.valueOf(idx));
             // ** Array subscript <index> is out of range. (26)
             ErrorManager.recordOrShowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
             ErrorManager.recordOrThrowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             return;
          }

          if (idx < 1 || idx > property.extent)
          {
             ErrorManager.recordOrShowError(7365,
                                            memberName,
                                            String.valueOf(property.extent),
                                            LegacyResource.BUFFER_FIELD);
             // <field> BUFFER-VALUE argument must be valid array index, less than or equal to field extent <extent>
             ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
             ErrorManager.recordOrThrowError(142, doGetName(), "");
             // ** Unable to update <filename> Field.
             return;
          }

          bufferFieldForProperty(property.name).changeValue((int) idx, value);
       }

       /**
        * This overloaded method is used when the dereference is the left-side of an assignment.
        * The implementation will extract the member from the collection and assign it the new value.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(long index, String memberName, Object value)
       {
          dereference(new int64(index), memberName, value);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record
        * into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the
        *          desired record.
        *
        * @return  <code>true</code> if it finds the record, else
        *          <code>false</code>.
        */
       public logical findByRowID(rowid id)
       {
          return findByRowID(id, LockType.LT_SHARE);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lockType
        *          Lock type
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        */
       public logical findByRowID(rowid id, LockType lockType)
       {
          final RecordBuffer buffer = buffer();
          final String dmoAlias = buffer.getDMOAlias();

          if (id.isUnknown())
          {
             String msg = "Invalid rowid for FIND-BY-ROWID method of BUFFER object " + buffer.getLegacyName();
             ErrorManager.recordOrShowError(7341, msg, false);

             return logical.FALSE;
          }

          try
          {
             boolean err = ErrorManager.silent(() ->
             {
                buffer.initialize();

                // manufacture a primary key sort clause; this will be discarded from the final FQL, but it is
                // needed by RAQ.initialize
                String sort = dmoAlias + "." + Session.PK + " asc";

                FindQuery findQuery = new FindQuery(buffer,
                                                    dmoAlias + "." + Session.PK + " = ?",
                                                    null,
                                                    sort,
                                                    new Object[] { id },
                                                    lockType);
                findQuery.unique();
             });

             return logical.of(!err);
          }
          catch (IllegalArgumentException | ErrorConditionException | QueryOffEndException exc)
          {
             // all ignored
          }

          return logical.FALSE;
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, long lock)
       {
          LockType lockType = fromLockNowait(lock, 0);

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, NumberType lock)
       {
          LockType lockType = fromLockNowait(lock.longValue(), 0);

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          NO-WAIT
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, long lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait);

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          NO-WAIT
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, NumberType lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait);

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          NO-WAIT
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, long lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait.longValue());

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          NO-WAIT
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, NumberType lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-BY-ROWID() method (KW_FIND_BR).
        * <p>
        * Locates the record with the rowid you specify, then moves the record into the buffer.
        *
        * @param   id
        *          An expression of type ROWID that represents the rowid of the desired record.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          NO-WAIT
        *
        * @return  {@code true} if it finds the record, else {@code false}.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findByRowID(rowid id, integer lock, int nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait);

          return lockType == null ? logical.FALSE : findByRowID(id, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds,
        *          else <code>false</code>.
        */
       public logical findFirst()
       {
          return _find(null, FindMode.FIRST, LockType.SHARE);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        */
       public logical findFirst(character predicate)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.FIRST, LockType.SHARE);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lockType
        *          Lock type
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds,
        *          else <code>false</code>.
        */
       public logical findFirst(character predicate, LockType lockType)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        */
       public logical findFirst(character predicate, long lock)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.FIRST, LockType.fromInt((int) lock));
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        */
       public logical findFirst(character predicate, NumberType lock)
       {
          LockType lockType = lock.isUnknown() ? LockType.SHARE : LockType.fromInt(lock.intValue());

          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findFirst(character predicate, long lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findFirst(character predicate, NumberType lock, long nowait)
       {
          long javaLock = lock.isUnknown() ? LockType.LT_SHARE : lock.longValue();

          LockType lockType = fromLockNowait(javaLock, nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findFirst(character predicate, long lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait.longValue());

          // toJavaType() returns null if predicate is unknown
          return lockType == null
            ? logical.FALSE
            : _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-FIRST() method (KW_FIND_1ST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-FIRST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findFirst(character predicate, NumberType lock, NumberType nowait)
       {
          long javaLock = lock.isUnknown() ? LockType.LT_SHARE : lock.longValue();

          LockType lockType = fromLockNowait(javaLock, nowait.longValue());

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.FIRST, lockType);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets the last record.
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       public logical findLast()
       {
          return _find(null, FindMode.LAST, LockType.SHARE);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       public logical findLast(character predicate)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.LAST, LockType.SHARE);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lockType
        *          Lock type
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       public logical findLast(character predicate, LockType lockType)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.LAST, lockType);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       public logical findLast(character predicate, long lock)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.LAST, LockType.fromInt((int) lock));
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        */
       public logical findLast(character predicate, NumberType lock)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.LAST, LockType.fromInt(lock.intValue()));
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findLast(character predicate, long lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.LAST, lockType);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findLast(character predicate, NumberType lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.LAST, lockType);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findLast(character predicate, long lock, NumberType nowait)
       {
          // TODO: add support for ? nowait mode
          LockType lockType = fromLockNowait(lock, nowait.longValue());

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.LAST, lockType);
       }

       /**
        * Conversion of FIND-LAST() method (KW_FIND_LST).
        * <p>
        * Gets a single record. This method lets a user get the last record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-LAST() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findLast(character predicate, NumberType lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
          // toJavaType() returns null if predicate is unknown
          return lockType == null ?
                logical.FALSE :
                _find(predicate.toJavaType(), FindMode.LAST, lockType);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This make sense only when there is only one record in the table.
        * Otherwise, the error 3166 "More than one &lt;tablename&gt; records found by a unique FIND."
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       public logical findUnique()
       {
          return _find(null, FindMode.UNIQUE, LockType.SHARE);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       public logical findUnique(character predicate)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.UNIQUE, LockType.SHARE);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lockType
        *          Lock type
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       public logical findUnique(character predicate, LockType lockType)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.UNIQUE, lockType);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       public logical findUnique(character predicate, long lock)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.UNIQUE, LockType.fromInt((int) lock));
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        */
       public logical findUnique(character predicate, NumberType lock)
       {
          // toJavaType() returns null if predicate is unknown
          return _find(predicate.toJavaType(), FindMode.UNIQUE, LockType.fromInt(lock.intValue()));
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findUnique(character predicate, long lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.UNIQUE, lockType);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findUnique(character predicate, NumberType lock, long nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait);

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.UNIQUE, lockType);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds,
        *          else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findUnique(character predicate, long lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock, nowait.longValue());

          // toJavaType() returns null if predicate is unknown
          return lockType == null
             ? logical.FALSE
             : _find(predicate.toJavaType(), FindMode.UNIQUE, lockType);
       }

       /**
        * Conversion of FIND-UNIQUE() method (KW_FIND_UNI).
        * <p>
        * Gets a single record. This method lets a user get a unique record that
        * satisfies the predicate expression.
        *
        * @param   predicate
        *          Predicate expression for find.
        * @param   lock
        *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
        * @param   nowait
        *          Integer value of one of the following: NO-WAIT, 0, or the Unknown value (?).
        *
        * @return  <code>true</code> if FIND-UNIQUE() succeeds,
        *          else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       public logical findUnique(character predicate, NumberType lock, NumberType nowait)
       {
          LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
          // toJavaType() returns null if predicate is unknown
          return lockType == null ?
                logical.FALSE :
                _find(predicate.toJavaType(), FindMode.UNIQUE, lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED attribute.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent()
       {
          return _current(LockType.SHARE);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lockType
        *          Lock type.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(LockType lockType)
       {
          if (lockType == null)
          {
             return logical.FALSE;
          }

          return _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(NumberType lock)
       {
          LockType lockType = fromLockNowait(lock.longValue(), 0);

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(long lock)
       {
          LockType lockType = fromLockNowait(lock, 0);

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lockType
        *          Lock type.
        * @param   noWait
        *          Zero to lock without waiting; non-zero to wait to acquire the lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(LockType lockType, long noWait)
       {
          if (lockType == null)
          {
             return logical.FALSE;
          }
          lockType = fromLockNowait(LockType.toInt(lockType), noWait);

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        * @param   noWait
        *          Zero to lock without waiting; non-zero to wait to acquire the lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(NumberType lock, long noWait)
       {
          if (lock == null)
          {
             return logical.FALSE;
          }
          LockType lockType = fromLockNowait(lock.longValue(), noWait);

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        * @param   noWait
        *          Zero to lock without waiting; non-zero to wait to acquire the lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(long lock, long noWait)
       {
          LockType lockType = fromLockNowait(lock, noWait);

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lockType
        *          Lock type.
        * @param   noWait
        *          Zero or unknown value to lock without waiting; non-zero to wait to acquire the
        *          lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(LockType lockType, NumberType noWait)
       {
          if (lockType == null || noWait == null)
          {
             return logical.FALSE;
          }
          lockType = fromLockNowait(LockType.toInt(lockType), noWait.longValue());

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        * @param   noWait
        *          Zero or unknown value to lock without waiting; non-zero to wait to acquire the
        *          lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(NumberType lock, NumberType noWait)
       {
          if (lock == null || noWait == null)
          {
             return logical.FALSE;
          }
          LockType lockType = fromLockNowait(lock.longValue(), noWait.longValue());

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of the FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Resets the CURRENT-CHANGED
        * attribute.
        *
        * @param   lock
        *          Lock type.
        * @param   noWait
        *          Zero or unknown value to lock without waiting; non-zero to wait to acquire the
        *          lock.
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code
        *          false}.
        */
       @LegacyMethod(name = "FIND-CURRENT")
       public logical findCurrent(long lock, NumberType noWait)
       {
          if (noWait == null)
          {
             return logical.FALSE;
          }
          LockType lockType = fromLockNowait(lock, noWait.longValue());

          return (lockType == null) ? logical.FALSE : _current(lockType);
       }

       /**
        * Conversion of NUM-FIELDS attribute (KW_NUM_FLD).
        * <p>
        * Getter of NUM-FIELDS attribute.
        *
        * @return  current value of NUM-FIELDS attribute.
        */
       public integer numFields()
       {
          return integer.of(_numFields());
       }

       /**
        * Disable the triggers associated with the FIND event for the buffer's table.
        */
       public void disableDumpTriggers()
       {
          final Class<?> dmoBuf = buffer().getDMOBufInterface();
          if (Buffer.class.isAssignableFrom(dmoBuf))
          {
             DatabaseTriggerManager.disableDumpTriggers((Class<? extends Buffer>) dmoBuf);
          }
       }

       /**
        * Disable the triggers associated with the CREATE, WRITE, DELETE and ASSIGN events for the
        * buffer's table.
        *
        * @param   allowReplication
        *          If <code>true</code> then only CREATE, DELETE, ASSIGN and WRITE triggers are
        *          disabled, and not REPLICATION-CREATE, REPLICATION-DELETE and REPLICATION-WRITE
        *          triggers.
        */
       public void disableLoadTriggers(boolean allowReplication)
       {
          final Class<?> dmoBuf = buffer().getDMOBufInterface();
          if (Buffer.class.isAssignableFrom(dmoBuf))
          {
             DatabaseTriggerManager.disableLoadTriggers((Class<? extends Buffer>) dmoBuf, allowReplication);
          }
       }

       /**
        * Conversion of CURRENT-ITERATION attribute (KW_CUR_ITER).
        * <p>
        * Getter of CURRENT-ITERATION attribute.
        *
        * @return  Current value of CURRENT-ITERATION attribute.
        */
       public handle currentIteration()
       {
          // TODO: implement, returning unknown values for the moment
          return new handle();

          // TODO: this method should return an integer instead
          // if (dataSet == null)
          // {
          //    return new integer();
          // }
          // else compute iteration
       }

       /**
        * Implements the GET-ITERATION() widget or buffer method.
        *
        * @param    level
        *           The level of the iteration to get.
        *
        * @return   The handle of the iteration, field-group or buffer.
        */
       @Override
       public handle getIteration(NumberType level)
       {
          return level == null || level.isUnknown() ? new handle() : getIteration(level.intValue());
       }

       /**
        * Implements the GET-ITERATION() buffer method.
        *
        * @param    level
        *           The level of the iteration to get.  Record number.
        *
        * @return   The handle of the iteration, field-group or buffer.
        */
       @Override
       public handle getIteration(int level)
       {
          // TODO: Implement me
          return new handle();
       }

       /**
        * Sets the ERROR-STRING attribute of 4GL temp-table.
        *
        * @param errStr The new value for the ERROR-STRING attribute.
        */
       @Override
       public void changeErrorString(character errStr)
       {
          RecordBuffer buf = buffer();
          if (!buf.isTemporary())
          {
             // permanent table case
             ErrorManager.recordOrShowError(11917, doGetName());
             // Cannot set ERROR if there is no BEFORE-TABLE for <name>.
             ErrorManager.recordOrShowError(3131, "ERROR-STRING", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!_available())
          {
             // temp-tables case
             ErrorManager.recordOrShowError(11897, doGetName());
             // <name> record is not available to set ERROR in.
             ErrorManager.recordOrShowError(3131, "ERROR-STRING", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!isBeforeBuffer())
          {
             if (!isAfterBuffer() || // SIMPLE BUFFER
                 (buf.peerRowid() == null || buf.peerRowid().isUnknown())) // no AFTER-IMAGE
             {
                ErrorManager.recordOrShowError(11918, doGetName(), "");
                // Cannot set ERROR if there is no BEFORE-TABLE row for <name>.
                return;
             }
          }

          buffer().errorString(errStr);
       }

       /**
        * Obtain the current ERROR-STRING attribute of 4GL temp-table.
        *
        * @return the current ERROR-STRING of the temp-table
        */
       @Override
       public character errorString()
       {
          RecordBuffer recBuf = buffer();
          if (!recBuf.isAvailable() || !recBuf.isTemporary())
          {
             return new character();
          }

          DataSet ds = _dataSet();
          if (ds == null)
          {
             return new character();
          }

          return recBuf.errorString();
       }

       /**
        * Returns the state of the ERROR attribute
        *
        * @return  Current state of the ERROR attribute.
        */
       @Override
       public logical error()
       {
          RecordBuffer recBuf = buffer();
          if (!recBuf.isAvailable() || !recBuf.isTemporary())
          {
             return logical.FALSE;
          }

          DataSet ds = _dataSet();
          if (ds == null)
          {
             return logical.FALSE;
          }

          // NOTE: internally, the P4GL database the __error-flag__ is an integer storing multiple flags.
          //       We need to extract the right bit (ERROR_ERROR) and convert it to a logical value.
          Integer flags = recBuf.errorFlags();
          return logical.of(flags != null && (flags & Buffer.ERROR_ERROR) != 0);
       }

       /**
        * Setter for {@code ERROR} attribute.<br>
        * The {@code ERROR} attribute is writable for a {@code Buffer} object. The attribute can be set
        * programmatically in order to manage the application response to errors.<br>
        * If set to {@code FALSE}, the application will ignore all errors that might be returned for a
        * statement using the {@code NO-ERROR} option.<br>
        * However, manually setting this attribute won't raise the {@code ERROR} condition.
        *
        * @param    value
        *           The new value of {@code ERROR} attribute.
        */
       @Override
       public void error(boolean value)
       {
          RecordBuffer recBuf = buffer();
          if (!_available())
          {
             // temp-tables case
             ErrorManager.recordOrShowError(11916, doGetName());
             // <name> record is not available to set ERROR in.
             ErrorManager.recordOrShowError(3131, "ERROR", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!recBuf.isTemporary())
          {
             // permanent table case
             ErrorManager.recordOrShowError(11917, doGetName());
             // Cannot set ERROR if there is no BEFORE-TABLE for <name>.
             ErrorManager.recordOrShowError(3131, "ERROR", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!isBeforeBuffer())
          {
             if (!isAfterBuffer() || // SIMPLE BUFFER
                 (recBuf.peerRowid() == null || recBuf.peerRowid().isUnknown())) // no AFTER-IMAGE
             {
                ErrorManager.recordOrShowError(11918, doGetName(), "");
                // Cannot set ERROR if there is no BEFORE-TABLE row for <name>.
                return;
             }
          }

          // NOTE: internally, the P4GL database the __error-flag__ is an integer storing multiple flags.
          //       We need to update the right bit (ERROR_ERROR) to new state (value).
          recBuf.updateErrorFlags(Buffer.ERROR_ERROR, value);
       }

       /**
        * Setter for ERROR attribute.
        *
        * @param   value
        *          The new value of ERROR attribute.
        */
       @Override
       public void error(logical value)
       {
          error(value.booleanValue());
       }

       /**
        * Returns the state of the DBNAME attribute
        *
        * @return  Current value of the DBNAME attribute.
        */
       public character getDbName()
       {
          final RecordBuffer buf = buffer();
          if (buf.getParentTable() != null)
          {
             // in the case of temp-tables (defined statically or dynamically)
             return new character("PROGRESST");
          }

          // permanent tables, it works as expected (the logical name of the database of the buffer)
          return new character(buf.getLogicalDatabase());
       }

       /**
        * Returns the state of the TABLE attribute
        *
        * @return    Current value of the TABLE attribute.
        */
       @Override
       public character getTable()
       {
          if (buffer().isTemporary())
          {
             AbstractTempTable table = (AbstractTempTable) tableHandleNative();
             return table.name();
          }
          else
          {
             return new character(buffer().getDmoInfo().legacyTable);
          }
       }

       /**
        * Conversion of TABLE-HANDLE attribute (KW_TAB_HAND).
        * <p>
        * Gets the handle of a temp-table object, if any, associated with the buffer object.
        *
        * @return  the handle of a temp-table object.
        */
       public handle tableHandle()
       {
          return buffer().tableHandle();
       }

       /**
        * Get the TEMP-TABLE resource associated with this buffer.
        *
        * @return   See above.
        */
       public TempTable tableHandleNative()
       {
          return buffer().tableHandleResource();
       }

       /**
        * Conversion of INDEX-INFORMATION method. Returns information about the first (according to
        * the definition order) table index, as a string.
        *
        * @return Information about the first (according to definition order) index, as a string. The
        *         returned comma-separated list consists of the following in the specified order:
        *         <ul>
        *         <li>the index name;
        *         <li>three integers of value 0 (false) or 1 (true) depending on whether the index is
        *             unique, primary or a word index;
        *         <li>the names of the index fields, each followed by a 0 (ascending) or 1
        *             (descending).
        *         </ul>
        *         An example: "index-one,1,1,0,field-one,0".
        */
       public character indexInformation()
       {
          return indexInformation(new integer(1));
       }

       /**
        * Conversion of INDEX-INFORMATION method. Returns information about the specified table index,
        * as a string.
        *
        * @param  indexNum
        *         1-based number of the index in the definition order.
        *
        * @return Information about the specified index, as a string. The returned comma-separated
        *         list consists of the following in the specified order:
        *         <ul>
        *         <li>the index name;
        *         <li>three integers of value 0 (false) or 1 (true) depending on whether the index is
        *             unique, primary or a word index;
        *         <li>the names of the index fields, each followed by a 0 (ascending) or 1
        *             (descending).
        *         </ul>
        *         An example: "index-one,1,1,0,field-one,0".
        */
       public character indexInformation(int indexNum)
       {
          return indexInformation(new integer(indexNum));
       }

       /**
        * Conversion of INDEX-INFORMATION method. Returns information about the specified table index,
        * as a string.
        *
        * @param  indexNum
        *         1-based number of the index in the definition order.
        *
        * @return Information about the specified index, as a string. The returned comma-separated
        *         list consists of the following in the specified order:
        *         <ul>
        *         <li>the index name;
        *         <li>three integers of value 0 (false) or 1 (true) depending on whether the index is
        *             unique, primary or a word index;
        *         <li>the names of the index fields, each followed by a 0 (ascending) or 1
        *             (descending).
        *         </ul>
        *         An example: "index-one,1,1,0,field-one,0".
        */
       public character indexInformation(NumberType indexNum)
       {
          if (indexNum.isUnknown())
          {
             return new character();
          }

          return new character(TableMapper.getLegacyIndexInfoString(this.buffer(), indexNum.intValue() - 1));
       }

       /**
        * Conversion of DYNAMIC attribute. Returns <code>true</code> if the buffer is dynamic.
        *
        * @return <code>true</code> if the buffer is dynamic.
        */
       @Override
       public boolean _dynamic()
       {
          return buffer().isDynamic();
       }

       /**
        * Conversion of DYNAMIC attribute. Returns <code>true</code> if the buffer is dynamic.
        *
        * @return <code>true</code> if the buffer is dynamic.
        */
       @Override
       public logical dynamic()
       {
          return logical.of(_dynamic());
       }

       /**
        * Get the value of the ADM-DATA attribute.
        *
        * @return   See above.
        */
       @Override
       public character getADMData()
       {
          return new character(this.admData);
       }

       /**
        * Set the value of the ADM-DATA attribute.
        *
        * @param    value
        *           The new value.
        */
       @Override
       public void setADMData(String value)
       {
          this.admData = value;
       }

       /**
        * Set the value of the ADM-DATA attribute.
        *
        * @param   value
        *          The new value.
        */
       @Override
       public void setADMData(character value)
       {
          this.admData = value.toJavaType();
       }

       /**
        * Gets the unique ID number associated to this object by the underlying system. Not the
        * same as the handle value itself.
        *
        * @return  An integer representing an unique id between all buffers ever created in the
        *          current process.
        */
       @Override
       public integer getUniqueID()
       {
          if (uniqueID == null)
          {
             uniqueID = UniqueIdGenerator.getUniqueId(IdKind.BUFFER);
          }

          return new integer(this.uniqueID);
       }

       /**
        * Returns the value of the NAMESPACE-URI attribute. Only for ELEMENT or ATTRIBUTE,
        * for other attributes return unknown value.
        *
        * @return  See above.
        */
       @Override
       public character namespaceURI()
       {
          return character.of(namespaceURI);
       }

       /**
        * Setter for NAMESPACE-URI attribute.
        *
        * @param   uri
        *          The new value of the NAMESPACE-URI attribute.
        */
       @Override
       public void namespaceURI(String uri)
       {
          if (uri == null)
          {
             ErrorManager.recordOrShowError(4083, "NAMESPACE-URI", LegacyResource.BUFFER + " widget");
             return;
          }

          namespaceURI = uri;
       }

       /**
        * Setter for NAMESPACE-URI attribute.
        *
        * @param   uri
        *          The new value of the NAMESPACE-URI attribute.
        */
       @Override
       public void namespaceURI(character uri)
       {
          namespaceURI(uri.toJavaType());
       }

       /**
        * Returns the value of the NAMESPACE-PREFIX attribute. Only for ELEMENT or ATTRIBUTE,
        * for other attributes return unknown value.
        *
        * @return  See above.
        */
       @Override
       public character namespacePrefix()
       {
          return character.of(namespacePrefix);
       }

       /**
        * Setter for NAMESPACE-PREFIX attribute.
        *
        * @param   prefix
        *          The new value of the NAMESPACE-PREFIX attribute.
        */
       @Override
       public void namespacePrefix(String prefix)
       {
          if (prefix == null)
          {
             ErrorManager.recordOrShowError(4083, "NAMESPACE-PREFIX", LegacyResource.BUFFER + " widget");
             return;
          }

          namespacePrefix = prefix;
       }

       /**
        * Setter for NAMESPACE-PREFIX attribute.
        *
        * @param   prefix
        *          The new value of the NAMESPACE-PREFIX attribute.
        */
       @Override
       public void namespacePrefix(character prefix)
       {
          namespacePrefix(prefix.toJavaType());
       }

       /**
        * Implementation of the read access of the {@code LABEL}.
        *
        * @return  The current value of the {@code LABEL}.
        */
       public character label()
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "queryable", LegacyResource.BUFFER + " widget");
          return character.UNKNOWN;
       }

       /**
        * Implementation of the write access of the {@code LABEL}.
        *
        * @param   sName
        *          the new value for the {@code LABEL}.
        */
       public void label(Text sName)
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.BUFFER + " widget");
       }

       /**
        * Implementation of the write access of the {@code LABEL}.
        *
        * @param  sName
        *         The new value for the {@code LABEL}.
        */
       public void label(String sName)
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.BUFFER + " widget");
       }

       /**
        * Implementation of the read access of the {@code SERIALIZE-NAME} attribute.
        *
        * @return  the current value of the {@code SERIALIZE-NAME} attribute.
        */
       public character getSerializeName()
       {
          DmoMeta dmoInfo = buffer().getDmoInfo();
          return serializeName == null || serializeName.trim().isEmpty() ? new character(dmoInfo.legacyTable) :
                                                                           new character(serializeName);
       }

       /**
        * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code SERIALIZE-NAME} attribute.
        */
       public void setSerializeName(Text sName)
       {
          setSerializeName(sName.toJavaType());
       }

       /**
        * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code SERIALIZE-NAME} attribute.
        */
       public void setSerializeName(String sName)
       {
          serializeName = sName;
       }

       /**
        * Implementation of the read access of the {@code XML-NODE-NAME} attribute.
        *
        * @return  the current value of the {@code XML-NODE-NAME} attribute.
        */
       public character getXmlNodeName()
       {
          return xmlNodeName == null || xmlNodeName.trim().isEmpty() ? getSerializeName() :
                                                                       new character(xmlNodeName);
       }

       /**
        * Implementation of the write access of the {@code XML-NODE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code XML-NODE-NAME} attribute.
        */
       public void setXmlNodeName(Text sName)
       {
          setXmlNodeName(sName.toJavaType());
       }

       /**
        * Implementation of the write access of the {@code XML-NODE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code XML-NODE-NAME} attribute.
        */
       public void setXmlNodeName(String sName)
       {
          xmlNodeName = sName;
       }

       /**
        * Obtain the XML Schema data type for the buffer-field object.
        *
        * @return the XML Schema data type for the buffer-field object.
        */
       @Override
       public character getXmlDataType()
       {
          handle.invalidAttribute("XML-DATA-TYPE", false, LegacyResource.BUFFER);
          return new character();
       }

       /**
        * Get the BUFFER:AUTO-SYNCHRONIZE attribute.
        *
        * @return  the value of BUFFER:AUTO-SYNCHRONIZE attribute.
        */
       @Override
       public logical autoSynchronize()
       {
          if (dataSet == null)
          {
             return logical.UNKNOWN;
          }

          return logical.of(autoSynchronize);
       }

       /**
        * Set the BUFFER:AUTO-SYNCHRONIZE attribute.
        */
       @Override
       public void autoSynchronize(boolean autoSyncronize)
       {
          if (dataSet == null)
          {
             return;
          }

          this.autoSynchronize = autoSyncronize;
       }

       /**
        * Set the BUFFER:AUTO-SYNCHRONIZE attribute.
        */
       @Override
       public void autoSynchronize(logical autoSyncronize)
       {
          if (autoSyncronize == null || autoSyncronize.isUnknown())
          {
             ErrorManager.recordOrShowError(4083, "AUTO-SYNCHRONIZE", "BUFFER widget");
             return;
          }

          autoSynchronize(autoSyncronize.booleanValue());
       }

       /**
        * Obtain the CAN-READ data type for the buffer-field object.
        *
        * @return the CAN-READ data type for the buffer-field object.
        */
       @Override
       public logical canRead()
       {
          UnimplementedFeature.missing("BUFFER:CAN-READ");
          return logical.UNKNOWN;
       }

       /**
        * Implementation of the NUM-ITERATIONS getter.
        *
        * @return  The current value of the NUM-ITERATIONS attribute.
        */
       @Override
       public integer numIterations()
       {
          UnimplementedFeature.missing("BUFFER:NUM-ITERATIONS");
          return new integer();
       }

       /**
        * Implementation of the NUM-REFERENCES getter.
        *
        * @return  The current value of the NUM-REFERENCES attribute.
        */
       @Override
       public integer numReferences()
       {
          UnimplementedFeature.missing("BUFFER:NUM-REFERENCES");
          return new integer();
       }

       /**
        * Configures the XML Schema data type for the buffer-field object.
        *
        * @param newType the new XML Schema data type for the buffer-field object.
        */
       @Override
       public void setXmlDataType(String newType)
       {
          handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.BUFFER);
       }

       /**
        * Configures the XML Schema data type for the buffer-field object.
        *
        * @param newType the new XML Schema data type for the buffer-field object.
        */
       @Override
       public void setXmlDataType(Text newType)
       {
          handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.BUFFER);
       }

       /**
        * Obtain the XML node type of this object. This represents the getter for {@code XML-NODE-TYPE} ABL
        * attribute.
        *
        * @return  the XML node type of this object.
        */
       @Override
       public character getXmlNodeType()
       {
          handle.invalidAttribute("XML-NODE-TYPE", false, LegacyResource.BUFFER);
          return new character();
       }

       /**
        * Allows specifying how this object will be represented in XML and XML Schema. Valid XML node types are
        * "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT". This represents the setter for {@code XML-NODE-TYPE} ABL
        * attribute.
        *
        * @param   newType
        *          The new type.
        */
       @Override
       public void setXmlNodeType(String newType)
       {
          handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.BUFFER);
       }

       /**
        * Allows specifying how this object will be represented in XML and XML Schema. Valid XML node types are
        * "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT". This represents the setter for {@code XML-NODE-TYPE} ABL
        * attribute.
        *
        * @param   newType
        *          The new type.
        */
       @Override
       public void setXmlNodeType(Text newType)
       {
          handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.BUFFER);
       }

       /**
        * Obtain the list of key fields of the buffer as a comma-separated list.
        *
        * @return  the list of key fields of the buffer. If the buffer is not part of a datasource, {@code rowid}
        *          is returned.
        */
       @Override
       public character keys()
       {
          if (parentDataSource == null || !parentDataSource.valid() || keys == null)
          {
             P2JIndex primaryIndex = buffer().getDmoInfo().getPrimaryIndex(true);
             if (primaryIndex == null || !primaryIndex.isUnique())
             {
                return new character(DataSource.ROWID_KEY);
             }

             StringBuilder sb = new StringBuilder();

             ArrayList<P2JIndexComponent> comps = primaryIndex.components();
             for (int i = 0; i < comps.size(); i++)
             {
                P2JIndexComponent comp = comps.get(i);
                if (sb.length() != 0)
                {
                   sb.append(",");
                }
                sb.append(comp.getLegacyName());
             }

             return new character(sb.toString());
          }

          StringBuilder sb = new StringBuilder();
          for (String key : keys)
          {
             if (Session.PK.equals(key))
             {
                continue;
             }

             if (sb.length() != 0)
             {
                sb.append(",");
             }
             sb.append(key);
          }

          return new character(sb.toString());
       }

       /**
        * Obtain the list of key fields of the buffer as a comma-separated list.
        *
        * @param   seqNum
        *          Ignored.
        *
        * @return  the list of key fields of the buffer.
        */
       @Override
       public character keys(long seqNum)
       {
          return keys();
       }

       /**
        * Obtain the list of key fields of the buffer as a comma-separated list.
        *
        * @param   seqNum
        *          Ignored.
        *
        * @return  the list of key fields of the buffer.
        */
       @Override
       public character keys(NumberType seqNum)
       {
          return keys();
       }

       /**
        * Set the CURRENT-ITERATION widget handle for this frame. This attribute is writable only for frames.
        *
        * @param   newIteration
        *          Ignored.
        */
       @Override
       public void setCurrentIteration(handle newIteration)
       {
          if (newIteration == null || newIteration.isUnknown())
          {
             ErrorManager.recordOrShowError(4083, "CURRENT-ITERATION", "BUFFER widget");
          }
          else
          {
             handle.invalidAttribute("CURRENT-ITERATION", true, LegacyResource.BUFFER);
          }
       }

       /**
        * Perform actual delete of an resource. At the time of this call, it is assumed the resource
        * is valid for deletion (the handle and the resource are both valid).
        * The method is overridden only to allow this object to maintain the structure of the chain of
        * BUFFERs (to update the PERMANENT-TABLE pointer if it was pointing to this buffer AND if the
        * {@link HandleChain#delete()} really disconnected it from the chain)
        */
       @Override
       public void delete()
       {
          // save next sibling because super.delete() will wipe it out
          HandleChain nextResource = this.nextSibling;

          super.delete();
          if (this.nextSibling != null)
          {
             // the super.delete() did not removed this object from chain
             return;
          }

          // special care for BUFFER resources: update the PERMANENT-TABLE pointer
          if (this == HandleChain.getHead(PERMANENT_TABLE))
          {
             HandleChain.setHead(PERMANENT_TABLE, nextResource);
          }
       }

       /**
        * Conversion of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular buffer of a query or DataSet object.
        *
        * @param   fieldNumber
        *          field number. An INTEGER expression representing the sequence
        *          number of the field in the buffer.
        *
        * @return  handle to a particular field in the buffer.
        */
       @Override
       public handle bufferField(NumberType fieldNumber)
       {
          if (fieldNumber == null || fieldNumber.isUnknown())
          {
             String msg = "Argument for BUFFER-FIELD method for buffer " +
                          buffer().getDmoInfo().legacyTable + " could not be evaluated";
             // untested at this time for 'database behavior' related to throwing structured exceptions
             // - see recordOrShowDatabaseError
             ErrorManager.recordOrShowError(7349, msg, false, false, false);
             return new handle();
          }

          return bufferField(fieldNumber.intValue());
       }

       /**
        * Conversion of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular buffer of a query or DataSet object.
        *
        * @param   fieldName
        *          A CHARACTER string expression representing the name of the field in the buffer.
        *
        * @return  handle to a particular field in the buffer.
        */
       @Override
       public handle bufferField(character fieldName)
       {
          if (fieldName.isUnknown())
          {
             String msg = "Argument for BUFFER-FIELD method for buffer " +
                          buffer().getDmoInfo().legacyTable + " could not be evaluated";
             // untested at this time for 'database behavior' related to throwing structured exceptions
             // - see recordOrShowDatabaseError
             ErrorManager.recordOrShowError(7349, msg, false, false, false);

             // TODO: if this message needs to be displayed, the code must be moved in other place
             // String errMsg2 =
             //    "Lead attributes in a chained-attribute expression (a:b:c) must be type " +
             //    "HANDLE or a user-defined type and valid (not UNKNOWN)";
             // ErrorManager.recordOrShowError(10068, errMsg2, false, false, false);
             return new handle();
          }
          else
          {
             return bufferField(fieldName.getValue());
          }
       }

       /**
        * Conversion of BUFFER-FIELD() method (KW_BUF_FLD).
        * <p>
        * Gets the handle to a particular buffer of a query or DataSet object.
        *
        * @param   fieldName
        *          A CHARACTER string expression representing the name of the field in the buffer.
        *
        * @return  handle to a particular field in the buffer.
        */
       @Override
       public handle bufferField(BaseDataType fieldName)
       {
          if (Text.class.isAssignableFrom(fieldName.getClass()))
          {
             return bufferField(fieldName.toStringMessage());
          }

          if (NumberType.class.isAssignableFrom(fieldName.getClass()))
          {
             return bufferField((NumberType) fieldName);
          }

          if (BinaryData.class.isAssignableFrom(fieldName.getClass()))
          {
             BinaryData bd = (BinaryData) fieldName;
             character nm = bd.getString(0);

             return bufferField(nm);
          }

          // otherwise, it uses the 'hex representation' of the value to compute a read a string.
          // we don't emulate this at this time.
          UnimplementedFeature.missing("BUFFER-POLY(incompatible-BDT) not implemented!");

          String msg = "BUFFER-FIELD " + fieldName + " was not found in buffer " +
                       buffer().getDmoInfo().legacyTable;
          ErrorManager.recordOrShowDatabaseError(7351, msg, false, false);

          // return unknown handle
          return new handle();
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Default field type mappings</li>
        * <li>LOOSE schema verification mode</li>
        * </ul>
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code True} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping)
       {
          return readXml(source, readMode, schemaLocation, overrideDefaultMapping, new character());
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>LOOSE schema verification mode</li>
        * </ul>
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code True} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        * @param   fieldTypeMapping
        *          Comma-delimited list of field name and data type name pairs to use to override
        *          data type mappings of specific fields. Specify unknown value to use default
        *          mappings.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping,
                              character fieldTypeMapping)
       {
          return readXml(source,
                         readMode,
                         schemaLocation,
                         overrideDefaultMapping,
                         fieldTypeMapping,
                         new character("LOOSE"));
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code True} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        * @param   fieldTypeMapping
        *          Comma-delimited list of field name and data type name pairs to use to override
        *          data type mappings of specific fields. Specify unknown value to use default
        *          mappings.
        * @param   verifySchemaMode
        *          Schema verification mode. Must evaluate to {@code IGNORE}, {@code LOOSE}, or
        *          {@code STRICT}. Default is {@code LOOSE}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping,
                              character fieldTypeMapping,
                              character verifySchemaMode)
       {
          RecordBuffer buffer = buffer();

          // sanity
          if (!buffer.isTemporary())
          {
             ErrorManager.recordOrShowError(13158);
             // XML Methods only allowed on TEMP-TABLE buffers.
             return logical.FALSE;
          }

          Map<String, String> mapping = null;
          if (fieldTypeMapping != null)
          {
             String javaStrMapping = fieldTypeMapping.toJavaType();
             if (javaStrMapping != null)
             {
                mapping = new HashMap<>();
                String[] splits = javaStrMapping.split(",");
                int len = splits.length;
                if (len == 0 || len % 2 == 1)
                {
                   ErrorManager.recordOrShowError(13200);
                   // Odd number of values in field-type-mapping argument.
                   return logical.of(false);
                }
                // no other validation at this time

                for (int k = 0; k < len; k += 2)
                {
                   mapping.putIfAbsent(splits[k], splits[k + 1]); // do not overwrite if field is duplicated
                }
             }
          }

          // normalize parameters
          XmlImport xmlImport = new XmlImport(source,
                                              readMode.toJavaType(),
                                              schemaLocation.toJavaType(),
                                              !overrideDefaultMapping.isUnknown() &&
                                               overrideDefaultMapping.booleanValue(),
                                              mapping,
                                              verifySchemaMode.toJavaType());

          // perform import
          return logical.of(xmlImport.importTable(this, RecordBuffer::loadRecord));
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Unformatted text</li>
        * <li>UTF-8 encoding</li>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target)
       {
          return writeXml(target, null, null, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>UTF-8 encoding</li>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target, logical formatted)
       {
          return writeXml(target, formatted, null, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target, logical formatted, character encoding)
       {
          return writeXml(target, formatted, encoding, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target, logical formatted, character encoding, character schemaLocation)
       {
          return writeXml(target, formatted, encoding, schemaLocation, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema)
       {
          return writeXml(target, formatted, encoding, schemaLocation, writeXmlSchema, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema)
       {
          return writeXml(target, formatted, encoding, schemaLocation, writeXmlSchema, minXmlSchema, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        * @param   writeBeforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema,
                               logical writeBeforeImage)
       {
          return writeXml(target,
                          formatted,
                          encoding,
                          schemaLocation,
                          writeXmlSchema,
                          minXmlSchema,
                          writeBeforeImage,
                          null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        * @param   beforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        * @param   noInitialVals
        *          {@code True} to omit data for fields whose values match their initial values.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema,
                               logical beforeImage,
                               logical noInitialVals)
       {
          RecordBuffer buffer = buffer();
          boolean ret;

          try
          {
             // sanity
             if (!buffer.isTemporary())
             {
                ErrorManager.recordOrShowError(13158);
                // XML Methods only allowed on TEMP-TABLE buffers.
                return logical.FALSE;
             }

             if (!buffer.getParentTable().flushBuffers())
             {
                return logical.FALSE;
             }

             XmlExport export = new XmlExport(
                   target,
                   formatted != null && !formatted.isUnknown() && formatted.getValue(),
                   encoding == null ? null : encoding.toJavaType());

             ret = export.writeTempTable(
                   (TemporaryBuffer) buffer,
                   schemaLocation == null ? null : schemaLocation.toJavaType(),
                   writeXmlSchema != null && !writeXmlSchema.isUnknown() && writeXmlSchema.getValue(),
                   minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
                   beforeImage != null && !beforeImage.isUnknown() && beforeImage.getValue(),
                   noInitialVals != null && !noInitialVals.isUnknown() && noInitialVals.getValue());

             buffer.release(false);
          }
          catch (XmlExport.Fatal e) // Emergency exit - for the 4GL compatibility
          {
             // Error was already reported, just stop;
             ret = false;
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log
                ret = false;
             }
          }
          return logical.of(ret);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target)
       {
          return writeXmlSchema(target, null, null, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target, logical formatted)
       {
          return writeXmlSchema(target, formatted, null, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target, logical formatted, Text encoding)
       {
          return writeXmlSchema(target, formatted, encoding, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        * @param   minXmlSchema
        *          If {@code true}, the minimum amount of schema for the object will be written.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target, logical formatted, Text encoding, logical minXmlSchema)
       {
          return writeXmlSchema(target, formatted, encoding, minXmlSchema, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        * @param   minXmlSchema
        *          If {@code true}, the minimum amount of schema for the object will be written.
        * @param   omitInitVal
        *          Use {@code true} to omit the initial values.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target,
                                     logical formatted,
                                     Text encoding,
                                     logical minXmlSchema,
                                     logical omitInitVal)
       {
          RecordBuffer buffer = buffer();
          boolean ret;

          try
          {
             // sanity
             if (!buffer.isTemporary())
             {
                ErrorManager.recordOrShowError(13158);
                // XML Methods only allowed on TEMP-TABLE buffers.
                return logical.FALSE;
             }

             XmlExport export = new XmlExport(
                   target,
                   formatted != null && !formatted.isUnknown() && formatted.getValue(),
                   encoding == null ? null : encoding.toJavaType());

                ret = export.writeTableSchema(
                         (TemporaryBuffer) buffer,
                         minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
                         omitInitVal != null && !omitInitVal.isUnknown() && omitInitVal.getValue());
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log
                ret = false;
             }
          }

          return logical.of(ret);
       }

       /**
        * Definition of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   targetFormat
        *          The format ("xml" or "json").
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the data for making it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical serializeRow(character  targetFormat,
                                   TargetData target,
                                   logical    formatted,
                                   character  encoding,
                                   logical    omitInitialValues)
       {
          return logical.of(serializeRowImpl(targetFormat.toJavaType(),
                                              target,
                                              formatted.toJavaType(),
                                              encoding.toJavaType(),
                                              omitInitialValues.toJavaType(),
                                              null));
       }

       /**
        * Definition of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   format
        *          The format ("xml" or "json").
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the data for making it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical serializeRow(character format, TargetData target, logical formatted, character encoding)
       {
          return logical.of(serializeRowImpl(
                format.toJavaType(), target, formatted.toJavaType(), encoding.toJavaType(), null, null));
       }

       /**
        * Definition of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   format
        *          The format ("xml" or "json").
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the data for making it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical serializeRow(character format, TargetData target, logical formatted)
       {
          return logical.of(serializeRowImpl(
                format.toJavaType(), target, formatted.toJavaType(), null, null, null));
       }

       /**
        * Definition of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   format
        *          The format ("xml" or "json").
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical serializeRow(character format, TargetData target)
       {
          return logical.of(serializeRowImpl(format.toJavaType(), target, null, null, null, null));
       }

       /**
        * Definition of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   targetFormat
        *          The format ("xml" or "json").
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the data for making it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuterObject
        *          {@code True} to omit data for the outermost object from the output.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical serializeRow(character  targetFormat,
                                   TargetData target,
                                   logical    formatted,
                                   character  encoding,
                                   logical    omitInitialValues,
                                   logical    omitOuterObject)
       {
          return logical.of(serializeRowImpl(targetFormat.toJavaType(),
                                              target,
                                              formatted.toJavaType(),
                                              encoding.toJavaType(),
                                              omitInitialValues.toJavaType(),
                                              omitOuterObject.toJavaType()));
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source, logical override)
       {
          return readXmlSchema(source, override, null, null);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        * @param   mapping
        *          A comma-delimited list of field name, data type pairs. This allows to specify the
        *          data type for a specific field.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source, logical override, Text mapping)
       {
          return readXmlSchema(source, override, mapping, null);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        * @param   mapping
        *          A comma-delimited list of field name, data type pairs. This allows to specify the
        *          data type for a specific field.
        * @param   verifyMode
        *          Specifies how the XML will be verified against the schema. Valid values: "LOOSE" or
        *          "STRICT".
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source, logical override, Text mapping, Text verifyMode)
       {
          return TempTableBuilder.asTempTable((Temporary) this).readXmlSchema(source,
                                                                              override,
                                                                              mapping,
                                                                              verifyMode);
       }

       /**
        * Implementation of the READ-JSON method. Reads JSON data from the specified data source into
        * this object.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>{@code MERGE} read mode</li>
        * </ul>
        *
        * @param   source
        *          Data source.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readJson(SourceData source)
       {
          return readJson(source, new character("MERGE"));
       }

       /**
        * Implementation of the READ-JSON method. Reads JSON data from the specified data source into
        * this object.
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readJson(SourceData source, character readMode)
       {
          if (!source.configureSupportedSources(SourceData.SD_READ_JSON,
                   LegacyResource.TEMP_TABLE + " widget"))
          {
             return logical.FALSE;
          }
          RecordBuffer buffer = buffer();

          // sanity
          if (!buffer.isTemporary())
          {
             ErrorManager.recordOrShowError(15378);
             // JSON methods only allowed on TEMP-TABLE buffers.
             return logical.FALSE;
          }

          // normalize parameters
          JsonImport jsonImport = new JsonImport(source, readMode.toJavaType());
          boolean success;

          try
          {
             // perform import
             success = jsonImport.readTable((TemporaryBuffer) buffer, RecordBuffer::loadRecord);
          }
          catch (PersistenceException exc)
          {
             // TODO: log
             success = false;
          }

          return logical.of(success);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Unformatted text</li>
        * <li>UTF-8 encoding</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target)
       {
          return writeJson(target,
                           logical.FALSE,
                           new character(),
                           logical.FALSE,
                           logical.FALSE,
                           logical.FALSE);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>UTF-8 encoding</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target, logical formatted)
       {
          return writeJson(target,
                           formatted,
                           new character(),
                           logical.FALSE,
                           logical.FALSE,
                           logical.FALSE);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target, logical formatted, character encoding)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           logical.FALSE,
                           logical.FALSE,
                           logical.FALSE);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           omitInitialValues,
                           logical.FALSE,
                           logical.FALSE);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuterObject
        *          {@code True} to omit data for the outermost object from the output.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues,
                                logical omitOuterObject)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           omitInitialValues,
                           omitOuterObject,
                           logical.FALSE);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuterObject
        *          {@code True} to omit data for the outermost object from the output.
        * @param   writeBeforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues,
                                logical omitOuterObject,
                                logical writeBeforeImage)
       {
          RecordBuffer buffer = buffer();
          boolean success;

          try
          {
             // sanity
             if (!buffer.isTemporary())
             {
                ErrorManager.recordOrShowError(15378);
                // JSON methods only allowed on TEMP-TABLE buffers.
                return logical.FALSE;
             }

             if (writeBeforeImage != null      &&
                 !writeBeforeImage.isUnknown() &&
                 writeBeforeImage.getValue())
             {
                ErrorManager.recordOrShowError(17366);
                // write-before-image not allowed for TEMP-TABLE or BUFFER object.
                ErrorManager.recordOrShowError(4065, false, "WRITE-JSON", "BUFFER widget");
                // **The <attribute> attribute on the <widget id> has invalid arguments.
                return logical.FALSE;
             }

             if (!buffer.getParentTable().flushBuffers())
             {
                return logical.FALSE;
             }

             com.fasterxml.jackson.core.JsonEncoding jsonEncoding = JsonExport.parseEncoding(encoding);
             if (jsonEncoding == null)
             {
                ErrorManager.recordOrShowError(15342, encoding.toStringMessage(), "WRITE-JSON");
                // Invalid encoding '<encoding>' argument for <method>.
                return logical.FALSE;
             }

             JsonExport export = new JsonExport();
             success = export.exportTempBuffer((TemporaryBuffer) buffer,
                                               target,
                                               formatted,
                                               jsonEncoding,
                                               omitInitialValues,
                                               omitOuterObject);

             buffer.release(false);
          }
          catch (PersistenceException exc)
          {
             // TODO: log
             success = false;
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log
                success = false;
             }
          }

          return logical.of(success);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other)
       {
          return copyTempTable(other, false, false, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  {@code true}  if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, logical append)
       {
          return copyTempTable(other, append, logical.FALSE, logical.FALSE, new character());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  {@code true} if operation is successful
        */
       @Override
       public logical copyTempTable(handle other, logical append, logical replace)
       {
          return copyTempTable(other, append, replace, logical.FALSE, new character());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, logical append, logical replace, logical looseCopy)
       {
          return copyTempTable(other, append, replace, looseCopy, new character());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  {@code true}  if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append)
       {
          return copyTempTable(other, append, false, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append, boolean replace)
       {
          return copyTempTable(other, append, replace, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append, boolean replace, boolean looseCopy)
       {
          return copyTempTable(other, append, replace, looseCopy, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * Only the first parameter is mandatory so this method will probably be heavy overloaded.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        * @param   prefix
        *          Prefix for naming the copied temp-table.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other,
                                    logical append,
                                    logical replace,
                                    logical looseCopy,
                                    character prefix)
       {
          TempTable tt = TempTableBuilder.asTempTable((Temporary) this);
          if (tt == null)
          {
             LOG.log(Level.SEVERE,
                     "Nothing to copy into, this dynamic TEMP-TABLE was not prepared yet.",
                     new Throwable());
             return logical.of(false);
          }
          return tt.copyTempTable(other, append, replace, looseCopy, prefix);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * Only the first parameter is mandatory so this method will probably be heavy overloaded.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        * @param   prefix
        *          Prefix for naming the copied temp-table.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other,
                                    boolean append,
                                    boolean replace,
                                    boolean looseCopy,
                                    String prefix)
       {
          TempTable tt = TempTableBuilder.asTempTable((Temporary) this);
          if (tt == null)
          {
             LOG.log(Level.SEVERE,
                     "Nothing to copy into, this dynamic TEMP-TABLE was not prepared yet.",
                     new Throwable());
             return logical.of(false);
          }
          return tt.copyTempTable(other,
                                  logical.of(append),
                                  logical.of(replace),
                                  logical.of(looseCopy),
                                  new character(prefix));
       }

       /**
        * Obtain the last associated query (to which this buffer was added or set).
        *
        * @return   The associated query or {@code unknown} if none exists.
        */
       @Override
       public handle getQueryAsHandle()
       {
          // a bit of maintenance
          if (lastQuery != null && !lastQuery.valid())
          {
             lastQuery = null;
          }

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

       /**
        * Set the query associated with the given resource. This is illegal when called directly by
        * P4GL application and will raise 4052 condition.
        *
        * @param   qry
        *          The query to associate.
        */
       @Override
       public void setQueryAsHandle(handle qry)
       {
          handle.invalidAttribute("QUERY", true, LegacyResource.BUFFER);
       }

       /**
        * Set the query associated with the given resource. This is illegal when called directly by
        * P4GL application and will raise 4052 condition.
        *
        * @param   qry
        *          The query to associate.
        */
       @Override
       public void setQueryAsHandle(QueryWrapper qry)
       {
          handle.invalidAttribute("QUERY", true, LegacyResource.BUFFER);
       }

       /**
        * Gets the maximum number of {@code DataSet} temp-table rows to retrieve in each {@code FILL}
        * operation. The default value is zero (which retrieves all rows that satisfy the associated
        * query).
        *
        * @return  the maximum number of DataSet temp-table rows to retrieve in each {@code FILL}
        *          operation.
        */
       @Override
       public integer batchSize()
       {
          return new integer(batchSize);
       }

       /**
        * Sets the maximum number of {@code DataSet} temp-table rows to retrieve in each {@code FILL}
        * operation.
        *
        * @param   size
        *          The new value for the {@code BATCH-SIZE} attribute.
        */
       @Override
       public void batchSize(long size)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(12361);
             // BATCH-SIZE may only be set for dataset temp-tables.
             return;
          }
          batchSize = (int) size;
       }

       /**
        * Sets the maximum number of {@code DataSet} temp-table rows to retrieve in each {@code FILL}
        * operation.
        *
        * @param   size
        *          The new value for the {@code BATCH-SIZE} attribute.
        */
       @Override
       public void batchSize(NumberType size)
       {
          if (size == null || size.isUnknown())
          {
             unableToAssignUnknown("BATCH-SIZE", "BUFFER widget");
             return;
          }

          batchSize(size.longValue());
       }

       /**
        * Gets the {@code ROWID} of the row in the after-image table that is the current version of
        * the row in the before-image table currently associated with this buffer. This row can
        * be a new or modified row.
        *
        * @return  the {@code ROWID} of the row in the after-image table that is the current version
        *          of the row in the before-image table
        */
       @Override
       public rowid afterRowid()
       {
          RecordBuffer recBuf = buffer();
          if (isAfterBuffer() || !recBuf.isAvailable())
          {
             // AFTER-ROWID is only available for BEFORE-BUFFERS of DATASETs
             return new rowid();
          }

          return recBuf.peerRowid();
       }

       /**
        * Gets the {@code ROWID} of the row in the before-image table that is the current version of
        * the row in the before-image table currently associated with this buffer. This row can
        * be a new or modified row.
        *
        * @return  the {@code ROWID} of the row in the before-image table that is the current version
        *          of the row in the before-image table
        */
       @Override
       public rowid beforeRowid()
       {
          RecordBuffer recBuf = buffer();
          if (isBeforeBuffer() || !recBuf.isAvailable())
          {
             // BEFORE-ROWID is only available for AFTER-BUFFERS of DATASETs
             return new rowid();
          }

          return recBuf.peerRowid();
       }

       /**
        * Obtain the current change state of the {@code DataSet} temp-table row associated with this
        * buffer object.
        *
        * @return  the current change state of the {@code DataSet} temp-table row associated with this
        *          buffer object.
        */
       @Override
       public integer rowState()
       {
          RecordBuffer recBuf = buffer();
          if (!recBuf.isAvailable() || !recBuf.isTemporary() || beforeBufferType == BeforeType.SIMPLE)
          {
             return new integer();
          }

          // even if there is no dataset for this table, the rowState can be read.
          return recBuf.rowState();
       }

       /**
        * Accepts changes to the data in one row of this {@code DataSet} temp-table.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical acceptRowChanges()
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11933, doGetName());
             // ACCEPT-ROW-CHANGES must be run on the BEFORE table of buffer <name>.
             return logical.FALSE;
          }

          if (!_available())
          {
             ErrorManager.recordOrShowError(11934, doGetName());
             // ACCEPT-ROW-CHANGES buffer <name> not available.
             return logical.FALSE;
          }

          int crtState = buffer().rowState().intValue();
          if (crtState == Buffer.ROW_MODIFIED || crtState == Buffer.ROW_CREATED)
          {
             // find and update the after-image/record
             BufferImpl after = afterBufferNative();
             after.findByRowID(this.afterRowid());
             RecordBuffer afterRecBuf = after.buffer();
             afterRecBuf.peerRowid(new rowid());
             afterRecBuf.rowState(Buffer.ROW_UNMODIFIED);
          }
          // otherwise (DELETE - after image is already deleted)

          // then drop the before-image data:
          setUpBeforeBuffer(true);
          deleteRecord();
          setUpBeforeBuffer(false);

          return logical.TRUE;
       }

       /**
        * Rejects changes to the data in one row of this {@code DataSet} temp-table.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical rejectRowChanges()
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11939, doGetName());
             // REJECT-ROW-CHANGES must be run on the BEFORE table of buffer <name>.
             return logical.FALSE;
          }

          if (!_available())
          {
             ErrorManager.recordOrShowError(11936, doGetName());
             // REJECT-ROW-CHANGES buffer <name> not available.
             return logical.FALSE;
          }

          // find and update the after-image/record
          BufferImpl after = afterBufferNative();
          TemporaryBuffer afterImage = (TemporaryBuffer) after.buffer();
          RecordBuffer recBuf = buffer();
          switch (recBuf.rowState().toJavaIntegerType())
          {
             case Buffer.ROW_CREATED:
                after.findByRowID(this.afterRowid());
                after.deleteRecord();
                break;
             case Buffer.ROW_DELETED:
                after.create();
                BaseRecord.copy(afterImage.getCurrentRecord(), recBuf.getCurrentRecord(), false);
                after.release();
                break;
             case Buffer.ROW_MODIFIED:
                after.findByRowID(this.afterRowid());
                BaseRecord.copy(afterImage.getCurrentRecord(), recBuf.getCurrentRecord(), false);
                afterImage.peerRowid(new rowid());
                afterImage.rowState(Buffer.ROW_UNMODIFIED);
                after.release();
                break;
          }

          // then drop the before-image data:
          setUpBeforeBuffer(true);
          deleteRecord();
          setUpBeforeBuffer(false);

          return logical.TRUE;
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data
        * source.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges()
       {
          return saveRowChangesImpl(0, null, false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffIndex
        *          The index of the buffer in the data source.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(NumberType buffIndex)
       {
          int idx = buffIndex.isUnknown() ? 1 : buffIndex.intValue();
          return saveRowChangesImpl(idx - 1, null, false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffName
        *          The name of the buffer in the data source.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(Text buffName)
       {
          return saveRowChangesImpl(buffName.toJavaType(), null, false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffIndex
        *          The index of the buffer in the data source.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(NumberType buffIndex, Text skipList)
       {
          int idx = buffIndex.isUnknown() ? 1 : buffIndex.intValue();
          return saveRowChangesImpl(idx - 1, skipList.toJavaType(), false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffName
        *          The name of the buffer in the data source.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(Text buffName, Text skipList)
       {
          return saveRowChangesImpl(buffName.toJavaType(), skipList.toJavaType(), false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        * <p>
        * <strong>Note:</strong> this form is used when unknown literal {@code ?} is used to specify
        *         the buffer. In either case, numeric or character, the meaning is the same.
        *
        * @param   buffInfo
        *          Unknown value.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(unknown buffInfo, Text skipList)
       {
          return saveRowChangesImpl(0, skipList.toJavaType(), false);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffIndex
        *          The index of the buffer in the data source.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        * @param   noLobs
        *          Use {@code true} to specify the BLOB and CLOB fields should be ignored in the
        *          save operation.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(NumberType buffIndex, Text skipList, logical noLobs)
       {
          boolean skipLobs = !noLobs.isUnknown() && noLobs.getValue();
          int idx = buffIndex.isUnknown() ? 1 : buffIndex.intValue();
          return saveRowChangesImpl(idx - 1, skipList.toJavaType(), skipLobs);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        * <p>
        * <strong>Note:</strong> this form is used when unknown literal {@code ?} is used to specify
        *         the buffer. In either case, numeric or character, the meaning is the same.
        *
        * @param   buffInfo
        *          Unknown value.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(unknown buffInfo, Text skipList, logical noLobs)
       {
          boolean skipLobs = !noLobs.isUnknown() && noLobs.getValue();
          return saveRowChangesImpl(0, skipList.toJavaType(), skipLobs);
       }

       /**
        * Saves changes from one row of the {@code DataSet} temp-table to the associated data source.
        *
        * @param   buffName
        *          The name of the buffer in the data source.
        * @param   skipList
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        * @param   noLobs
        *          Use {@code true} to specify the BLOB and CLOB fields should be ignored in the
        *          save operation.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical saveRowChanges(Text buffName, Text skipList, logical noLobs)
       {
          boolean skipLobs = !noLobs.isUnknown() && noLobs.getValue();
          return saveRowChangesImpl(buffName.toJavaType(), skipList.toJavaType(), skipLobs);
       }

       /**
        * Obtain the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member
        * buffer. The default mode is {@code MERGE}.
        *
        * @return  the fill mode for this buffer.
        */
       @Override
       public character fillMode()
       {
          if (dataSet == null)
          {
             // FILL-MODE is unknown for 'free' buffers
             return new character();
          }

          return new character(fillMode);
       }

       /**
        * Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet}
        * member buffer. The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE},
        * {@code NO-FILL}, and {@code REPLACE}.
        *
        * @param   mode
        *          The new fill mode for this buffer.
        */
       @Override
       public void fillMode(String mode)
       {
          if (mode == null)
          {
             unableToAssignUnknown("FILL-MODE", "BUFFER widget");
             return;
          }

          if (dataSet == null)
          {
             ErrorManager.recordOrShowError(11959);
             // FILL-MODE is only valid for a dataset buffer.
             ErrorManager.recordOrShowError(4058, "FILL-MODE", "BUFFER widget", mode);
             // **Attribute <attribute> for the <widget id> has an invalid value of <string>.
             return;
          }

          String upperMode = mode.toUpperCase();
          switch (upperMode)
          {
             case FM_APPEND:
             case FM_EMPTY:
             case FM_MERGE:
             case FM_NOFILL:
             case FM_REPLACE:
                // one of these is expected
                break;
             default:
                // but this is not. Complain about it and use default, MERGE
                ErrorManager.recordOrShowError(11960);
                // FILL-MODE must be one of NO-FILL, MERGE, EMPTY, etc.
                ErrorManager.recordOrShowError(4058, "FILL-MODE", "BUFFER widget", mode);
                // **Attribute <attribute> for the <widget id> has an invalid value of <string>.
                upperMode = FM_MERGE;
          }

          this.fillMode = upperMode;
       }

       /**
        * Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet}
        * member buffer. The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE},
        * {@code NO-FILL}, and {@code REPLACE}.
        *
        * @param   mode
        *          the new fill mode for this buffer.
        */
       @Override
       public void fillMode(Text mode)
       {
          fillMode(mode == null ? null : mode.toJavaType());
       }

       /**
        * Attaches a {@code DataSource} object to this temp-table {@code Buffer} in a {@code DataSet}
        * object.
        *
        * @param   hds
        *          The handle to the {@code DataSource} to be attached.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical attachDataSource(handle hds)
       {
          return attachDataSource(hds, null, null, null);
       }

       /**
        * Attaches a {@code DataSource} object to this temp-table {@code Buffer} in a {@code DataSet}
        * object.
        *
        * @param   hds
        *          The handle to the {@code DataSource} to be attached.
        * @param   pairList
        *          A comma-delimited list of field name pairs that specify a mapping between different
        *          field names in the {@code DataSource} buffer and the {@code DataSet} temp-table
        *          buffer.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical attachDataSource(handle hds, character pairList)
       {
          return attachDataSource(hds, pairList, null, null);
       }

       /**
        * Attaches a {@code DataSource} object to this temp-table {@code Buffer} in a {@code DataSet}
        * object.
        *
        * @param   hds
        *          The handle to the {@code DataSource} to be attached.
        * @param   pairList
        *          A comma-delimited list of field name pairs that specify a mapping between different
        *          field names in the {@code DataSource} buffer and the {@code DataSset} temp-table
        *          buffer.
        * @param   exceptFields
        *          A comma-separated list of fields in the {@code DataSet} object buffer that will not
        *          be populated with data from the data source.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical attachDataSource(handle hds, character pairList, character exceptFields)
       {
          return attachDataSource(hds, pairList, exceptFields, null);
       }

       /**
        * Attaches a {@code DataSource} object to this temp-table {@code Buffer} in a {@code DataSet}
        * object. This method provides much of the internal definition for the {@code FILL}. Among
        * other things, this method creates the {@code FILL} query.
        * <p>
        * This Java method corresponds to ABL {@code ATTACH-DATA-SOURCE} method with all 4 parameters.
        *
        * @param   hds
        *          The handle to the {@code DataSource} to be attached.
        * @param   pairList
        *          A comma-delimited list of field name pairs that specify a mapping between different
        *          field names in the {@code DataSource} buffer and the {@code DataSet} temp-table buffer.
        * @param   exceptFields
        *          A comma-separated list of fields in the {@code DataSet} object buffer that will not
        *          be populated with data from the data source.
        * @param   includeFields
        *          A comma-separated list of fields to include in the {@code DataSet} object buffer,
        *          as an ALTERNATIVE to specifying fields to exclude in except-fields.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical attachDataSource(handle hds,
                                       character pairList,
                                       character exceptFields,
                                       character includeFields)
       {
          // validate parameters
          if (hds == null || !hds._isValid() || !hds.isType(LegacyResource.DATA_SOURCE))
          {
             ErrorManager.recordOrShowError(11957);
             // ATTACH-DATA-SOURCE must have a valid data-source handle as the first argument.
             return logical.FALSE;
          }

          DataSource newDSrc = (DataSource) hds.getResource();
          if (newDSrc._getNumSourceBuffers() == 0)
          {
             ErrorManager.recordOrShowError(11958);
             // ATTACH-DATA-SOURCE may not be called until source buffers have been assigned to the DATA-SOURCE.
             return logical.FALSE;
          }

          if (exceptFields != null && !exceptFields.isUnknown() && !exceptFields.getValue().isEmpty() &&
              includeFields != null && !includeFields.isUnknown() && !includeFields.getValue().isEmpty())
          {
             ErrorManager.recordOrShowError(12779);
             // Cannot have both exclude list and include list.
             return logical.FALSE;
          }

          Set<String> incFields = null;
          Set<String> excFields = null;
          if (exceptFields != null && !exceptFields.isUnknown() && !exceptFields.getValue().isEmpty())
          {
             excFields = new CaseInsensitiveHashSet<>();
             // note: TAB, CR, LF are treated like normal character, only SPACE and , are separators
             StringTokenizer st = new StringTokenizer(exceptFields.toJavaType(), " ,", false);
             while (st.hasMoreTokens())
             {
                String tok = st.nextToken();
                int i = tok.indexOf('.');
                if (i != -1)
                {
                   // .f1 is OK, <not-a-table>.f1 is also OK, f1 is not
                   excFields.add(tok.substring(i + 1));
                }
                else
                {
                   excFields.add(tok);
                }
             }
          }
          else if (includeFields != null && !includeFields.isUnknown() && !includeFields.getValue().isEmpty())
          {
             incFields = new CaseInsensitiveHashSet<>();
             // note: TAB, CR, LF are treated like normal character, only SPACE and , are separators
             StringTokenizer st = new StringTokenizer(includeFields.toJavaType(), " ,", false);
             while (st.hasMoreTokens())
             {
                String tok = st.nextToken();
                int i = tok.indexOf('.');
                if (i != -1)
                {
                   // .f1 is OK, <not-a-table>.f1 is also OK, f1 is not
                   incFields.add(tok.substring(i + 1));
                }
                else
                {
                   incFields.add(tok);
                }
             }
          }

          // silently return true if same data source is assigned
          if (newDSrc == dataSource)
          {
             return logical.TRUE;
          }

          // this must be a DataSet buffer
          if (dataSet == null)
          {
             ErrorManager.recordOrShowError(11952);
             // ATTACH-DATA-SOURCE is only valid for buffers that are members of a dataset.
             return logical.FALSE;
          }

          dstFieldMeta = new CaseInsensitiveHashMap<>();
          // pre-compute field indexes into the record data array
          DmoMeta dstMeta = buffer().getDmoInfo();
          RecordMeta recordMeta = dstMeta.getRecordMeta();
          PropertyMeta[] props = recordMeta.getPropertyMeta(false);
          for (int i = 0, propsLength = props.length; i < propsLength; i++)
          {
             PropertyMeta pmeta = props[i];
             dstFieldMeta.put(pmeta.getDmoProperty().legacyLower, pmeta);
          }

          // analyze field mapping between this buffer and those from new DataSource
          String pairListStr = null;
          ArrayList<TableMapper.LegacyFieldInfo> myFields = TableMapper.getLegacyOrderedList(this, true);

          // The algorithm for creating the full list of pairs of fields takes into consideration the method
          // parameters (the explicit pair list, except and include list of fields) but also analyses the list
          // of fields in both current and data-source buffers for matching names. It looks something like this:
          //    * analyse/validate the provided list and mark those fields;
          //          - if a field name is found in multiple source buffers mark all of them;
          //    * for each source buffer
          //          - iterate all destination fields in the order of definition
          //                # if the pair is in attach list, use the pair
          //                # if fields have different name, skip the pair
          //                # if one of the fields is marked, skip the pair
          //                # otherwise (same name, not marked), use the pair.
          //
          // This algorithm will cause some interesting results. For example:
          //    * if attach-list is empty/unknown and each of N source and destination buffers have a commonly
          //          named field (f1), there will be N pairs (the destination field f1 is overwritten N time);
          //    * in the above scenario, if one of the source fields with same name is paired with a different
          //          field from destination (f2), all of the above pairs will be dropped. The field f1 remains
          //          unaffected, even if there are N-1 fields to pair from source buffers.
          int numSourceBuffers = newDSrc._getNumSourceBuffers();
          BufferImpl[] buffers = new BufferImpl[numSourceBuffers];
          DmoMeta[] metaData = new DmoMeta[numSourceBuffers];
          List<Pair<String, String>>[] orderedPairlist = new ArrayList[numSourceBuffers];
          List<Pair<String, DataSource.FieldReference>>[] mappedNamesMap = new ArrayList[numSourceBuffers];
          for (int k = 1; k <= numSourceBuffers; k++)
          {
             // fewer calls to buffer / getSourceBufferNative / getDmoInfo are better, that is why we precompute
             BufferImpl buffer = newDSrc.getSourceBufferNative(k);
             buffers[k - 1] = buffer;
             metaData[k - 1] = buffer.buffer().getDmoInfo();
             orderedPairlist[k - 1] = new ArrayList<>();
          }

          Set<String> marked = new HashSet<>(); // the marked fields are not matched automatically
          if (pairList != null && !pairList.isUnknown())
          {
             // validate the list of field pairs
             pairListStr = pairList.getValue();
             String[] splits = pairListStr.isEmpty() ? new String[0] : pairListStr.split(",");
             if (splits.length % 2 == 1)
             {
                ErrorManager.recordOrShowError(9035);
                // Pairs list had too many pairs or odd number of entries.
                return logical.FALSE;
             }

             String key = null;
             for (String field : splits)
             {
                final String origField = field;
                if (StringHelper.startsWithIgnoreCase(field, "rowid("))
                {
                   field = field.substring(field.indexOf('(') + 1, field.indexOf(')')) + "." + Session.PK;
                }
                else
                {
                   int k = field.indexOf('.');
                   if (k != -1)
                   {
                      marked.add(field.substring(k + 1).toLowerCase());
                   }
                   else
                   {
                      marked.add(field.toLowerCase());
                   }
                }

                if (key == null)
                {
                   key = field;
                }
                else
                {
                   String keyNoEx = key.indexOf('[') == -1 ? key : key.substring(0, key.indexOf('['));
                   String valNoEx = field.indexOf('[') == -1 ? field : field.substring(0, field.indexOf('['));

                   if (!hasFieldEx(keyNoEx))
                   {
                      if (!hasFieldEx(valNoEx))
                      {
                         ErrorManager.recordOrShowError(9034, key);
                         // Pair with <member> failed to have a member in each table.
                         return logical.FALSE;
                      }

                      // swap [key] and [field] so that [key] is in destination (current) buffer
                      String tmp = key;
                      key = field;
                      field = tmp;
                      tmp = keyNoEx;
                      keyNoEx = valNoEx;
                      valNoEx = tmp;
                   }

                   // lookup the buffer for [field] / [valNoEx]
                   int srcBufferIndex = 0;
                   for (int k = 1; k <= numSourceBuffers; k++)
                   {
                      BufferImpl buffer = newDSrc.getSourceBufferNative(k);
                      if (buffer.hasFieldEx(valNoEx))
                      {
                         if (field.indexOf('.') == -1)
                         {
                            field = buffer.doGetName() + "." + field;
                         }
                         srcBufferIndex = k;
                         break;
                      }
                   }
                   if (srcBufferIndex == 0)
                   {
                      // the [fld2] could not be found in any of the buffers of DataSource
                      ErrorManager.recordOrShowError(9034, origField);
                      // Pair with <member> failed to have a member in each table.
                      return logical.FALSE;
                   }

                   if (key.indexOf('.') == -1)
                   {
                      key = this.doGetName() + "." + key;
                   }
                   orderedPairlist[srcBufferIndex - 1].add(Pair.of(key, field));
                   key = null;
                }
             }
          }

          for (int k = 0; k < numSourceBuffers; k++)
          {
             DmoMeta bufMeta = metaData[k];
             mappedNamesMap[k] = new ArrayList<>();
             for (TableMapper.LegacyFieldInfo myField : myFields)
             {
                String fieldName = myField.getLegacyName();
                if (marked.contains(myField.getLcLegacyName()))
                {
                   continue;
                }

                if (bufMeta.byLegacyName(fieldName) != null)
                {
                   if (incFields != null && !incFields.contains(fieldName) ||
                       excFields != null && excFields.contains(fieldName))
                   {
                      continue; // drop it!
                   }

                   Property prop2 = bufMeta.byLegacyName(fieldName);
                   Class<? extends BaseDataType> ftype2 = (prop2 == null) ? null : (Class) prop2._fwdType;
                   Integer offset2 = null;
                   if (prop2 != null)
                   {
                      offset2 = bufMeta.getRecordMeta().getIndexOfProperty(prop2.name);
                   }
                   // TODO: pass the Property to FieldReference ctor?
                   mappedNamesMap[k].add(
                         Pair.of(fieldName,
                                 new DataSource.FieldReference(buffers[k], fieldName, ftype2, offset2)));
                }
             }
          }

          for (int k = 0; k < mappedNamesMap.length; k++)
          {
             BufferImpl buffer = buffers[k];
             DmoMeta bufMeta = metaData[k];
             //List<Pair<String, DataSource.FieldReference>> pairs = mappedNamesMap[k];
             for (Pair<String, String> pair : orderedPairlist[k])
             {
                // normalize [fld1] a nd [fld2]
                String fld1 = pair.getKey();
                int j = fld1.indexOf('.');
                if (j > 0)
                {
                   fld1 = fld1.substring(j + 1);
                }

                String fld2 = pair.getValue();
                j = fld2.indexOf('.');
                if (j > 0)
                {
                   fld2 = fld2.substring(j + 1);
                }
                Property prop2 = bufMeta.byLegacyName(fld2);
                Class<? extends BaseDataType> ftype2 = (prop2 == null) ? null : (Class) prop2._fwdType;
                Integer offset2 = (prop2 == null) ? null : bufMeta.getRecordMeta().getIndexOfProperty(prop2.name);
                // TODO: pass the Property to FieldReference ctor?
                mappedNamesMap[k].add(Pair.of(fld1.toLowerCase(),
                                              new DataSource.FieldReference(buffer, fld2, ftype2, offset2)));
             }
          }

          // cache the DATA-SOURCE-COMPLETE-MAP value
          dataSrcMapping = new ArrayList<>();
          StringBuilder sb = new StringBuilder();
          String myName = (name == null) ? "?" : name;

          for (List<Pair<String, DataSource.FieldReference>> pairs : mappedNamesMap)
          {
             for (Pair<String, DataSource.FieldReference> pair : pairs)
             {
                String ciName = pair.getKey();
                DataSource.FieldReference dataSrcField = pair.getValue();
                dataSrcMapping.add(Pair.of(ciName, dataSrcField));
                if (sb.length() != 0)
                {
                   sb.append(',');
                }

                sb.append(myName).append('.').append(ciName).append(',');
                if (Session.PK.equals(dataSrcField.getName()))
                {
                   sb.append("rowid(").append(dataSrcField.getBuffer()._name()).append(")");
                }
                else
                {
                   sb.append(dataSrcField.getBuffer()._name()).append('.').append(dataSrcField.getName());

                   if (dataSrcField.getIndex() > 0)
                   {
                      sb.append("[").append(dataSrcField.getIndex()).append("]");
                   }
                }
             }
          }
          dataSrcMappingStr = sb.toString()/*.toLowerCase()*/;

          // all fine, create the links between objects:
          this.dataSource = newDSrc;
          if (isAfterBuffer())
          {
             this.peerBuffer.dataSource = newDSrc;
          }
          this.attachedPairlist = pairListStr;
          dataSource.attach(this, dataSrcMapping);

          if (dataSource.hasExplicitQuery())
          {
             QueryWrapper query = (QueryWrapper) dataSource.getQueryAsHandle().getResource();
             query.setSubstitutionBuffers(this);
          }

          return logical.TRUE;
       }

       /**
        * Detaches the {@code DataSource} from the temp-table buffer in a {@code DataSet} object.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical detachDataSource()
       {
          detachDataSourceImpl();

          return logical.TRUE;
       }

       /**
        * Get the handle to the {@code DataSource} object currently attached to the {@code DataSet}
        * object buffer.
        *
        * @return  The value of {@code DATA-SOURCE} as described above.
        */
       @Override
       public handle dataSource()
       {
          return new handle(dataSource);
       }

       /**
        * Get the handle to the {@code DataSource} object currently attached to the {@code DataSet}
        * object buffer.
        *
        * @return  The value of {@code DATA-SOURCE} as described above.
        */
       public DataSource dataSourceNative()
       {
          return dataSource;
       }

       /**
        * Obtain the handle to the default buffer of the after-image table that corresponds to
        * the buffer of the before-image table currently associated with this buffer handle.
        *
        * @return  The value of {@code AFTER-BUFFER} attribute as described above.
        */
       @Override
       public handle afterBuffer()
       {
          return new handle(afterBufferNative());
       }

       /**
        * Obtain the default buffer of the after-image table that corresponds to
        * the buffer of the before-image table currently associated with this buffer.
        *
        * @return  The value of {@code AFTER-BUFFER} attribute as described above.
        */
       public BufferImpl afterBufferNative()
       {
          if (!isBeforeBuffer())
          {
             // permanent and simple temp-tables do not have BEFORE/AFTER buffers
             return null;
          }

          return peerBuffer;
       }

       /**
        * Obtain the {@code rowid} of the row in the original before-image table that corresponds to
        * the row in the change table currently associated with this buffer handle.
        * <p>
        * Implementation note:
        *    FWD uses as a storage backing for both {@code ORIGIN-ROWID} and {@code DATA-SOURCE-ROWID}
        *    the same field ({@link TempRecord#_originRowid}) exclusively.
        *    <ul>
        *       <li>when the record is a normal dataset temp-table record the meaning is
        *            {@code DATA-SOURCE-ROWID}, for both BEFORE and AFTER image. In the case the
        *            {@code DataSource} must be also attached or an ABL error is raised. </li>
        *       <li>when we use it as a CHANGE dataset temp-table, the meaning of the field is
        *             {@code ORIGIN-ROWID}.</li>
        *   </ul>
        *
        * @return  The value of {@code ORIGIN-ROWID} attribute as described above.
        *
        * @see     #dataSourceRowid()
        */
       @Override
       public rowid originRowid()
       {
          RecordBuffer recBuf = buffer();

          if (!recBuf.isTemporary() || !recBuf.isAvailable())
          {
             return new rowid();
          }

          if (!tableHandleNative().getOriginHandle()._isValid())
          {
             return new rowid();
          }

          if (!recBuf.isTemporary() || !recBuf.isAvailable())
          {
             return new rowid();
          }

          TempRecord record = (TempRecord) recBuf.getCurrentRecord();
          return new rowid(record._originRowid());
       }

       /**
        * Obtain the handle to the default buffer of the before-image table that corresponds to
        * the buffer of the after-image table currently associated with this buffer handle.
        *
        * @return  The value of {@code BEFORE-BUFFER} attribute as described above.
        */
       @Override
       public handle beforeBuffer()
       {
          return new handle(beforeBufferNative());
       }

       /**
        * Obtain the default buffer of the before-image table that corresponds to
        * the buffer of the after-image table currently associated with this buffer.
        *
        * @return  The value of {@code BEFORE-BUFFER} attribute as described above.
        */
       public BufferImpl beforeBufferNative()
       {
          if (!isAfterBuffer())
          {
             // permanent and simple temp-tables do not have BEFORE/AFTER buffers
             return null;
          }

          return peerBuffer;
       }

       /**
        * The list of field name pairs for all fields  (in comm-delimited format) in a {@code DataSet}
        * temp-table buffer that are mapped to corresponding fields in an attached data-source object.
        *
        * @return  The value of {@code DATA-SOURCE-COMPLETE-MAP} attribute.
        */
       @Override
       public character dataSourceCompleteMap()
       {
          if (dataSet == null)
          {
             ErrorManager.recordOrShowError(12780, false);
             // DATA-SOURCE-COMPLETE-MAP only legal for dataset tables.
             return new character();
          }

          if (dataSource == null)
          {
             ErrorManager.recordOrShowError(12781, false);
             // DATA-SOURCE-COMPLETE-MAP requires prior ATTACH-DATA-SOURCE method to be run.
             return new character();
          }

          return new character(dataSrcMappingStr);
       }

       /**
        * Obtains the value of {@code DATA-SOURCE-ROWID} attribute (the {@code DataSource} row corresponding to
        * this buffer object).
        *
        * @return  The current value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public rowid dataSourceRowid()
       {
          if (dataSource == null || !dataSource.valid())
          {
             ErrorManager.recordOrShowError(14276, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return new rowid();
          }

          if (dataSet == null || !dataSet.valid())
          {
             if (!isBeforeBuffer() || peerBuffer.dataSet == null || !peerBuffer.dataSet.valid())
             {
                ErrorManager.recordOrShowError(14275, false, doGetName());
                // Dataset record for <name> required for DATA-SOURCE-ROWID
                return new rowid();
             }
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return new rowid();
          }

          TempRecord record = (TempRecord) buffer.getCurrentRecord();
          Long bruteRow = record._datasourceRowid();
          if (bruteRow == null)
          {
             return new rowid();
          }

          // validate the [bruteRow]: it must belong to an existing record of only buffer of the current datasource
          BufferImpl srcBuf = dataSource.getSourceBufferNative();
          try
          {
             Record srcRec = buffer.getPersistence().getSession(Persistence.TEMP_CTX)
                                   .get(srcBuf.buffer().getDMOImplementationClass(), bruteRow);
             if (srcRec == null)
             {
                return new rowid();
             }
          }
          catch (PersistenceException e)
          {
             return new rowid();
          }

          return new rowid(bruteRow);
       }

       /**
        * Obtains the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   joinLevel
        *          The level of join, if the data-source is a join.
        *
        * @return  The current value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public rowid dataSourceRowid(long joinLevel)
       {
          return dataSourceRowid(new int64(joinLevel));
       }

       /**
        * Obtains the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   buffName
        *          The name of one of the data-source buffers.
        *
        * @return  The current value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public rowid dataSourceRowid(String buffName)
       {
          return dataSourceRowid(new character(buffName));
       }

       /**
        * Obtains the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   joinLevel
        *          The level of join, if the data-source is a join.
        *
        * @return  The current value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public rowid dataSourceRowid(NumberType joinLevel)
       {
          if (dataSource == null || !dataSource.valid())
          {
             ErrorManager.recordOrShowError(14276, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return new rowid();
          }

          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return new rowid();
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return new rowid();
          }

          if (joinLevel.isUnknown() ||
              joinLevel.intValue() == 1 && dataSource._getNumSourceBuffers() == 1)
          {
             return dataSourceRowid();
          }

          int jjl = joinLevel.intValue();
          if (jjl < 1 || jjl > dataSource._getNumSourceBuffers())
          {
             ErrorManager.recordOrShowError(14277, "", "");
             // Unable to identify DATA-SOURCE buffer <temp-table name> in DATA-SOURCE-ROWID. (14277)
             return new rowid();
          }

          BufferImpl buf = dataSource.getSourceBufferNative(jjl);
          String findDataSource = dataSource.getSaveWhereStringImpl(buf, false);
          if (findDataSource == null)
          {
             ErrorManager.recordOrShowError(14278, buf.doGetName(), "");
             // Unable to generate FIND for <source-name> in DATA-SOURCE-ROWID. (14278)
             return new rowid();
          }

          logical ok = new logical();
          ErrorManager.silent(() -> ok.assign(
                buf._find(findDataSource, FindMode.UNIQUE, LockType.EXCLUSIVE, BufferImpl.this)));

          if (ok.isUnknown() || !ok.getValue() || !buf._available())
          {
             return new rowid();
          }

          return buf.rowID();
       }

       /**
        * Obtains the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   buffName
        *          The name of one of the data-source buffers.
        *
        * @return  The current value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public rowid dataSourceRowid(character buffName)
       {
          if (dataSource == null || !dataSource.valid())
          {
             ErrorManager.recordOrShowError(14276, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return new rowid();
          }

          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return new rowid();
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return new rowid();
          }

          BufferImpl buf = null;
          if (!buffName.isUnknown())
          {
             String javaType = buffName.toJavaType();
             int n = dataSource._getNumSourceBuffers();
             for (int k = 1; k <= n; k++)
             {
                BufferImpl buff = dataSource.getSourceBufferNative(k);
                if (buff.doGetName().equalsIgnoreCase(javaType))
                {
                   buf = buff;
                   break;
                }
             }
          }

          if (buf == null)
          {
             ErrorManager.recordOrShowError(14277, "", "");
             // Unable to identify DATA-SOURCE buffer <temp-table name> in DATA-SOURCE-ROWID. (14277)
             return new rowid();
          }

          if (dataSource._getNumSourceBuffers() == 1)
          {
             return dataSourceRowid();
          }

          String findDataSource = dataSource.getSaveWhereStringImpl(buf, false);
          if (findDataSource == null)
          {
             ErrorManager.recordOrShowError(14278, buf.doGetName(), "");
             // Unable to generate FIND for <source-name> in DATA-SOURCE-ROWID. (14278)
             return new rowid();
          }

          logical ok = new logical();
          BufferImpl finalBuf = buf;
          ErrorManager.silent(() -> ok.assign(
                finalBuf._find(findDataSource, FindMode.UNIQUE, LockType.EXCLUSIVE, BufferImpl.this)));

          if (ok.isUnknown() || !ok.getValue() || !finalBuf._available())
          {
             return new rowid();
          }

          return finalBuf.rowID();
       }

       /**
        * Configures the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   val
        *          The new value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public void dataSourceRowid(rowid val)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return;
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return;
          }

          if (isBeforeBuffer())
          {
             peerBuffer.dataSourceRowid(val);
             return;
          }

          setDatasourceRowidImpl(val.getValue());

          // TODO: set the _datasourceRowid on the peer record (AFTER / BEFORE)
       }

       /**
        * Configures the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   joinLevel
        *          The level of join, if the data-source is a join.
        * @param   val
        *          The new value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public void dataSourceRowid(int64 joinLevel, rowid val)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return;
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return;
          }

          if (isBeforeBuffer())
          {
             peerBuffer.dataSourceRowid(joinLevel, val);
             return;
          }

          UnimplementedFeature.missing("BUFFER:DATA-SOURCE-ROWID(n)= not implemented");
       }

       /**
        * Configures the {@code DataSource} row corresponding to this buffer object.
        *
        * @param   buffName
        *          The name of one of the data-source buffers.
        * @param   val
        *          The new value of {@code DATA-SOURCE-ROWID} ABL attribute.
        */
       @Override
       public void dataSourceRowid(character buffName, rowid val)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID
             return;
          }

          RecordBuffer buffer = buffer();
          if (!buffer.isTemporary() || !buffer.isAvailable())
          {
             return;
          }

          if (isBeforeBuffer())
          {
             peerBuffer.dataSourceRowid(buffName, val);
             return;
          }

          UnimplementedFeature.missing("BUFFER:DATA-SOURCE-ROWID(name)= not implemented");
       }

       /**
        * Get the {@code DataSet} object of which the buffer is a member or {@code null} otherwise.
        *
        * @return  the {@code DataSet} object of which the buffer is a member.
        */
       public DataSet _dataSet()
       {
          return isBeforeBuffer() ? peerBuffer.ref().dataSet : dataSet;
       }

       /**
        * Get the handle for the {@code DataSet} object of which the buffer is a member.
        *
        * @return  handle for the {@code DataSet} object of which the buffer is a member.
        */
       @Override
       public handle dataSet()
       {
          return new handle(_dataSet());
       }

       /**
        * Counts the ACTIVE relations for which the buffer is the parent. A buffer may be a parent in
        * multiple relations, but a child in only one.
        *
        * @return  The value of {@code NUM-CHILD-RELATIONS} attribute as described above.
        */
       @Override
       public integer numChildRelations()
       {
          if (dataSet == null)
          {
             // quick out, this is no dataset buffer
             return new integer(0);
          }

          List<DataRelation> rels = dataSet.getRelations(this, true, false, true);
          return new integer(rels.size());
       }

       /**
        * Obtain the handle to the ACTIVE parent data-relation object for the parent of this buffer.
        * Since a buffer may be a child in only one ACTIVE relation, there can be maximum only one
        * parent for any buffer at any time.
        *
        * @return  The value of {@code PARENT-RELATION} attribute as described above.
        */
       @Override
       public handle parentRelation()
       {
          return new handle(parentRelationNative());
       }

       /**
        * Obtain the handle to the ACTIVE parent data-relation object for the parent of this buffer.
        * Since a buffer may be a child in only one ACTIVE relation, there can be maximum only one
        * parent for any buffer at any time.
        *
        * @return  The value of {@code PARENT-RELATION} attribute as described above.
        */
       public DataRelation parentRelationNative()
       {
          if (dataSet == null)
          {
             // quick out, this is no dataset buffer
             return null;
          }

          List<DataRelation> rels = dataSet.getRelations(this, false, true, true);
          if (rels.isEmpty())
          {
             return null;
          }

          for (DataRelation rel : rels)
          {
             if (rel._isActive())
             {
                return rel;
             }
          }

          return null;
       }

       /**
        * Creates a blank before-table record for each buffer record, with a {@code ROW-STATE} of
        * {@code ROW-CREATED} for a table with only new data.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical markNew()
       {
          if (!isAfterBuffer())
          {
             ErrorManager.recordOrShowError(14274);
             // MARK-ROW-STATE and MARK-NEW may only be used on a dataset member buffer with an existing BEFORE-TABLE.
             return logical.FALSE;
          }

          return logical.of(((TemporaryBuffer) buffer()).markNew(beforeBufferNative()));
       }

       /**
        * Manually set the {@code ROW-STATE} attribute for the current record/row.
        *
        * @param   newState
        *          The new state the record is marked with. Legal values: {@code ROW_CREATED},
        *          {@code ROW_MODIFIED}, {@code ROW_DELETED}, and {@code ROW_UNMODIFIED}.
        *
        * @return {@code true} on success.
        */
       @Override
       public logical markRowState(int64 newState)
       {
          int jState = (newState == null || newState.isUnknown()) ?
                Buffer.ROW_UNMODIFIED :
                newState.intValue();
          return markRowState(jState, null);
       }

       /**
        * Manually set the {@code ROW-STATE} attribute for the current record/row.
        *
        * @param   newState
        *          The new state the record is marked with. Legal values: {@code ROW_CREATED},
        *          {@code ROW_MODIFIED}, {@code ROW_DELETED}, and {@code ROW_UNMODIFIED}.
        *
        * @return {@code true} on success.
        */
       @Override
       public logical markRowState(long newState)
       {
          return markRowState(newState, null);
       }

       /**
        * Manually set the {@code ROW-STATE} attribute for the current record/row.
        *
        * @param   newState
        *          The new state the record is marked with. Legal values: {@code ROW_CREATED},
        *          {@code ROW_MODIFIED}, {@code ROW_DELETED}, and {@code ROW_UNMODIFIED}.
        * @param   before
        *          A handle to the before-image source buffer.
        *
        * @return {@code true} on success.
        */
       @Override
       public logical markRowState(int64 newState, handle before)
       {
          int jState = (newState == null || newState.isUnknown()) ?
                Buffer.ROW_UNMODIFIED :
                newState.intValue();
          return markRowState(jState, before);
       }

       /**
        * Manually set the {@code ROW-STATE} attribute for the current record/row.
        *
        * @param   newState
        *          The new state the record is marked with. Legal values: {@code ROW_CREATED},
        *          {@code ROW_MODIFIED}, {@code ROW_DELETED}, and {@code ROW_UNMODIFIED}.
        * @param   compareTo
        *          A handle to the before-image source buffer - used to compare to current data.
        *
        * @return {@code true} on success.
        */
       @Override
       public logical markRowState(long newState, handle compareTo)
       {
          if (!isAfterBuffer())
          {
             ErrorManager.recordOrShowError(14274);
             // MARK-ROW-STATE and MARK-NEW may only be used on a dataset member buffer with an existing BEFORE-TABLE.
             return logical.FALSE;
          }

          if (newState == Buffer.ROW_UNMODIFIED)
          {
             return logical.TRUE; // nothing to process
          }
          else if (newState != Buffer.ROW_CREATED  &&
                   newState != Buffer.ROW_MODIFIED &&
                   newState != Buffer.ROW_DELETED)
          {
             ErrorManager.recordOrShowError(14288);
             // Invalid ROW-STATE given to MARK-ROW-STATE.
             return logical.FALSE;
          }

          if (!_available())
          {
             ErrorManager.recordOrShowError(14275, false, doGetName());
             // Dataset record for <name> required for DATA-SOURCE-ROWID.
             return logical.FALSE;
          }

          RecordBuffer compRB = null;
          BufferImpl compBuffer = null;
          if (compareTo != null && !compareTo.isUnknown())
          {
             if (!compareTo.isType(LegacyResource.BUFFER))
             {
                ErrorManager.recordOrShowError(14289);
                // Invalid handle or handle type for MARK-ROW-STATE
                return logical.FALSE;
             }

             compBuffer = (BufferImpl) compareTo.getResource();
             if (!compBuffer._available())
             {
                ErrorManager.recordOrShowError(0, "MARK-ROW-STATE called with an empty BEFORE-BUFFER");
                // Progress OpenEdge crashes.
                return logical.FALSE;
             }
             if (compBuffer.buffer().isTemporary())
             {
                compRB = compBuffer.buffer();
             }
          }

          integer rowState = rowState();
          if (!rowState.isUnknown() && rowState.intValue() != Buffer.ROW_UNMODIFIED)
          {
             return logical.TRUE; // already has before image/record
          }

          BufferImpl b4Buffer = beforeBufferNative();
          RecordBuffer b4RecBuf = b4Buffer.buffer();
          RecordBuffer recBuf = buffer();
          b4Buffer.setUpBeforeBuffer(true);
          switch ((int) newState)
          {
             case ROW_CREATED:
                b4Buffer.create();
                recBuf.rowState((int) newState);
                recBuf.peerRowid(b4RecBuf.rowID());
                b4RecBuf.peerRowid(recBuf.rowID());
                b4RecBuf.rowState((int) newState);
                b4Buffer.release();
                break;
             case ROW_MODIFIED:
                b4Buffer.create();
                if (compRB != null)
                {
                   RecordBuffer.copyCommonFields(b4RecBuf.getCurrentRecord(), compRB.getCurrentRecord());
                }
                else
                {
                   RecordBuffer.copyCommonFields(b4RecBuf.getCurrentRecord(), recBuf.getCurrentRecord());
                }
                recBuf.rowState((int) newState);
                recBuf.peerRowid(b4RecBuf.rowID());
                b4RecBuf.peerRowid(recBuf.rowID());
                b4RecBuf.rowState((int) newState);
                b4Buffer.release();
                break;
             case ROW_DELETED:
                // this buffer is not updated
                b4Buffer.create();
                if (compRB != null)
                {
                   RecordBuffer.copyCommonFields(b4RecBuf.getCurrentRecord(), compRB.getCurrentRecord());
                }
                else
                {
                   RecordBuffer.copyCommonFields(b4RecBuf.getCurrentRecord(), recBuf.getCurrentRecord());
                }
                b4RecBuf.peerRowid(new rowid());
                b4RecBuf.rowState((int) newState);
                b4Buffer.release();
                break;
          }
          b4Buffer.setUpBeforeBuffer(false);

          return logical.TRUE;
       }

       /**
        * Obtain the handle to a {@code DataRelation} object for which the buffer is the parent.
        *
        * @param   index
        *          The index of the data-relation object (1-based).
        *
        * @return  the handle to a {@code DataRelation} object for which the buffer is the parent.
        */
       @Override
       public handle childRelation(long index)
       {
          if (dataSet == null || index < 1)
          {
             // quick out, this is no dataset buffer
             return new handle();
          }

          List<DataRelation> rels = dataSet.getRelations(this, true, false, true);
          if (index > rels.size())
          {
             return new handle();
          }

          return new handle(rels.get((int) (index - 1)));
       }

       /**
        * Obtain the handle to a {@code DataRelation} object for which the buffer is the parent.
        *
        * @param   index
        *          The index of the data-relation object (1-based).
        *
        * @return  the handle to a {@code DataRelation} object for which the buffer is the parent.
        */
       @Override
       public handle childRelation(int64 index)
       {
          if (index == null || index.isUnknown())
          {
             return new handle();
          }

          return childRelation(index.longValue());
       }

       /**
        * Definition of the SYNCHRONIZE method.
        *
        * @return   <code>true</code> if the synchronization was performed.
        */
       @Override
       public logical querySynchronize()
       {
          if (dataSet == null)
          {
             return logical.FALSE;
          }
          List<DataRelation> childRelations = dataSet.getRelations(this, true, false, true);
          boolean ok = true;
          for (DataRelation rel : childRelations)
          {
             BufferImpl childBuffer = rel.getChildBufferNative();
             if (!rel.isRecursive().booleanValue() || childBuffer != this)
             {
                synchronize(rel);
                ok &= childBuffer.querySynchronize().booleanValue();
             }
          }
          return logical.of(ok);
       }

       /**
        * Obtain a comma-separated list of field name pairs for fields in a {@code DataSet}
        * temp-table buffer that are mapped to corresponding fields in an attached {@code DataSource}
        * object. This list includes only the field name pairs you specified with the most recently
        * attached data-source object, in the order they were specified.
        * <p>
        * @return  the list of attach fields, as described above.
        */
       @Override
       public character attachedPairlist()
       {
          return new character(attachedPairlist);
       }

       /**
        * Returns the state of the REJECTED attribute
        *
        * @return Current state of the REJECTED attribute.
        */
       @Override
       public logical rejected()
       {
          RecordBuffer recBuf = buffer();
          if (!recBuf.isAvailable() || !recBuf.isTemporary())
          {
             return logical.FALSE;
          }

          DataSet ds = _dataSet();
          if (ds == null)
          {
             return logical.FALSE;
          }

          // NOTE: internally, the P4GL database the __error-flag__ is an integer storing multiple flags.
          //       We need to extract the right bit (ERROR_REJECTED) and convert it to a logical value.
          Integer flags = recBuf.errorFlags();
          return logical.of(flags != null && (flags & Buffer.ERROR_REJECTED) != 0);
       }

       /**
        * Setter for REJECTED attribute.
        *
        * @param value The new value of REJECTED attribute.
        */
       @Override
       public void rejected(boolean value)
       {
          RecordBuffer recBuf = buffer();

          if (!_available())
          {
             // temp-tables case
             ErrorManager.recordOrShowError(11916, doGetName());
             // <name> record is not available to set ERROR in.
             ErrorManager.recordOrShowError(3131, "REJECTED", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!recBuf.isTemporary())
          {
             // permanent table case
             ErrorManager.recordOrShowError(11917, doGetName());
             // Cannot set ERROR if there is no BEFORE-TABLE for <name>.
             ErrorManager.recordOrShowError(3131, "REJECTED", "", LegacyResource.BUFFER);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!isBeforeBuffer())
          {
             if (!isAfterBuffer() || // SIMPLE BUFFER
                 (recBuf.peerRowid() == null || recBuf.peerRowid().isUnknown())) // no AFTER-IMAGE
             {
                ErrorManager.recordOrShowError(11918, doGetName(), "");
                // Cannot set ERROR if there is no BEFORE-TABLE row for <name>.
                return;
             }
          }

          // NOTE: internally, the P4GL database the __error-flag__ is an integer storing multiple flags.
          //       We need to update the right bit (ERROR_ERROR) to [value].
          recBuf.updateErrorFlags(Buffer.ERROR_REJECTED, value);
       }

       /**
        * Setter for REJECTED attribute.
        *
        * @param value The new value of REJECTED attribute.
        */
       @Override
       public void rejected(logical value)
       {
          if (value == null || value.isUnknown())
          {
             unableToAssignUnknown("REJECTED", "BUFFER widget");
             return;
          }

          rejected(value.getValue());
       }

       /**
        * Fills a {@code Buffer} object, recursively, based on its defined data sources.
        *
        * @return {@code true} if operation is successful.
        */
       @Override
       public logical fill()
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(11945);
             // BUFFER:FILL is only valid for buffers that are members of a dataset.
             return logical.FALSE;
          }

          lastBatch = false;

          if (FM_NOFILL.equalsIgnoreCase(fillMode))
          {
             // nothing to do
             return logical.TRUE;
          }

          return new FillWorker().fill();
       }

       /**
        * Synchronize child query of the DATA-RELATION.
        *
        * @param rel
        *        The DATA-RELATION in question
        */
       private boolean synchronize(DataRelation rel)
       {
          Buffer childBuffer = rel.getChildBufferNative();
          String[] relFields = rel.getRelationFields(false);
          String[] parentRelFields = rel.getRelationFields(true);
          String cname = childBuffer.name().toJavaType();
          String mname = this.name;
          StringBuilder sb = new StringBuilder("FOR EACH ").append(cname).append(" WHERE ");
          for (int nf = 0; nf < relFields.length; ++nf) {
             if (nf > 0)
             {
                sb.append(" and ");
             }
             sb.append(cname).append(".").append(relFields[nf]).append(" = ").
                append(mname).append(".").append(parentRelFields[nf]);
          }
          handle hQuery = new handle(false);
          QueryWrapper.createQuery(hQuery);
          P2JQuery query = hQuery.unwrapQuery();
          query.addBuffer(childBuffer);
          query.prepare(sb.toString());
          query.hintFullResults();
          query.queryOpen();
          boolean rc = query.getFirst().booleanValue();
          query.queryClose();
          HandleOps.delete(hQuery);
          return rc;
       }

       /**
        * Assigns the value of {@code srcBuffer.df} to field {@code f} of this buffer. Takes care of the extents.
        *
        * @param   myField
        *          The destination field of this buffer.
        * @param   df
        *          The source field.
        * @param   srcBuffer
        *          The source buffer.
        * @param   dstRecBuff
        *          The destination record buffer.
        * @param   fields
        *          A map of resolved {@link FieldStructure} for each destination field.
        */
       private void assign(String                      myField,
                           DataSource.FieldReference   df,
                           BufferImpl                  srcBuffer,
                           RecordBuffer                dstRecBuff,
                           Map<String, FieldStructure> fields)
       {
          if (!srcBuffer._available())
          {
             return;
          }

          FieldStructure field = fields.get(myField);

          if (field == null)
          {
             int dstIndex = 0;
             String crtField;
             int i = myField.indexOf('[');
             if (i != -1)
             {
                int myFieldSize = myField.length();
                char[] crtFieldChars = new char[i];
                boolean openParenthesis = false;
                for (int j = 0; j < myFieldSize; j++)
                {
                   char current = myField.charAt(j);
                   if (current == '[')
                   {
                      openParenthesis = true;
                   }
                   else if (current == ']')
                   {
                      break;
                   }
                   else if (openParenthesis == false)
                   {
                      crtFieldChars[j] = current;
                   }
                   else if (openParenthesis == true)
                   {
                      dstIndex = dstIndex * 10 + ((int) current - '0');
                   }
                }

                crtField = new String(crtFieldChars);
             }
             else
             {
                crtField = myField;
             }

             DmoMeta dmoInfo = dstRecBuff.getDmoInfo();
             Property dstProp = dmoInfo.byLegacyName(crtField);

             if (dstProp == null)
             {
                return; // something is wrong... invalid [crtField] name. Raise OE condition?
             }

             field = new FieldStructure();
             field.dstProp = dstProp;
             field.crtField = crtField;
             field.dstIndex = dstIndex;
             fields.put(myField, field);
          }

          // TRACKING-CHANGES can't be active during FILL, so BEFORE-TABLE is not needed

          // only TEMP-TABLES can perform FILL, so no triggers can be invoked

          // this assign is wrapped in a start/endBatch bracket, so no validation/flush is done on individual field
          // assignment

          // TODO: extent

          PropertyMeta dstPropMeta = field.dstProp.getMeta();
          Integer dstFieldOffset = dstPropMeta == null ? null : dstPropMeta.getOffset();
          Integer srcFieldOffset = df.getOffset();
          RecordBuffer srcRecBuff = srcBuffer.buffer();
          boolean directAssign = dstPropMeta != null &&
                                 df.getType() != null &&
                                 df.getType() == dstPropMeta.getType();

          if (field.dstProp.extent == 0)
          {
             if (df.getIndex() == 0)
             {
                Object src = null;

                if (directAssign && srcFieldOffset != null)
                {
                   src = srcRecBuff.getDirectField(srcFieldOffset);
                }
                else
                {
                   src = srcBuffer.dereference(df.getName());
                }

                if (directAssign && dstFieldOffset != null)
                {
                   dstRecBuff.setDirectField(src, dstFieldOffset);
                }
                else
                {
                   dereference(field.crtField, src);
                }
             }
             else
             {
                dereference(field.crtField, srcBuffer.dereference(df.getIndex() /* -1? */ , df.getName()));
             }
          }
          else
          {
             if (field.dstIndex != 0)
             {
                if (df.getIndex() == 0)
                {
                   dereference(field.dstIndex, field.crtField, srcBuffer.dereference(df.getName()));
                }
                else
                {
                   dereference(field.dstIndex /* -1? */,
                               field.crtField,
                               srcBuffer.dereference(df.getIndex() /* -1? */ , df.getName()));
                }
             }
             else
             {
                // bulk copy: (assume) both source and destination are extent fields
                for (int k = 0; k < field.dstProp.extent; k++)
                {
                   BufferField srcField = srcBuffer.bufferFieldNative(df.getName());
                   BufferField destField = bufferFieldNative(field.crtField);

                   destField.changeValue(k + 1, srcField.value(k + 1));
                }
             }
          }
       }

       /**
        * Applies a callback procedure, which allows execution of a defined event without duplicating
        * the event procedure definition.
        *
        * @param   eventName
        *          The event whose callback will be called.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical applyCallback(String eventName)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(11949);
             // APPLY-CALLBACK is only for buffers that are members of datasets.
             return logical.FALSE;
          }

          if (!isCallbackName(eventName))
          {
             return logical.FALSE;
          }

          return logical.of(invokeCallback(eventName.toUpperCase()));
       }

       /**
        * Retrieves the name of the internal procedure associated with the ABL callback for the
        * specified event.
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return the name of the internal procedure associated with specified event.
        */
       @Override
       public character getCallbackProcName(String eventName)
       {
          if (!isCallbackName(eventName))
          {
             return new character();
          }

          CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
          if (cbd == null)
          {
             return new character();
          }

          return new character(cbd.getTarget());
       }

       /**
        * Retrieves the handle of the procedure that contains the internal procedure associated
        * with the ABL callback for the specified event
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  a handle to the procedure that contains the callback procedure.
        */
       @Override
       public handle getCallbackProcContext(String eventName)
       {
          if (!isCallbackName(eventName))
          {
             return new handle();
          }

          CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
          if (cbd == null || cbd.getProcedure() == null)
          {
             return new handle();
          }

          return new handle(cbd.getProcedure());
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param    event
        *           The event name, must evaluate to one of the events supported by implementing object.
        * @param    proc
        *           The internal procedure to be executed for this event, which will be executed using
        *           the current THIS-PROCEDURE reference. To remove a callback mapping, set this
        *           parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, String proc)
       {
          return setCallbackProcedure(event, proc, null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, character proc)
       {
          return setCallbackProcedure(event, proc.toJavaType(), null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, character proc)
       {
          return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is
        * not a valid handle.
        *
        * @param    event
        *           The event name, must evaluate to one of the events supported by implementing object.
        * @param    proc
        *           The internal procedure to be executed for this event. To remove a callback
        *           mapping, set this parameter to the empty string.
        * @param    context
        *           The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, String proc, handle context)
       {
          return setCallbackProcedure(event.toJavaType(), proc, context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is
        * not a valid handle.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set this
        *          parameter to the empty string.
        * @param   context
        *          The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, character proc, handle context)
       {
          return setCallbackProcedure(event, proc.toJavaType(), context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is not a valid
        * handle.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using the current
        *          THIS-PROCEDURE reference. To remove a callback mapping, set this parameter to the empty
        *          string.
        * @param   context
        *          The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, character proc, handle context)
       {
          return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), context);
       }

       /**
        * Applies a callback procedure, which allows execution of a defined event without duplicating
        * the event procedure definition.
        *
        * @param   eventName
        *          The event whose callback will be called.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical applyCallback(Text eventName)
       {
          return applyCallback(eventName.toJavaType());
       }

       /**
        * Retrieves the name of the internal procedure associated with the ABL callback for the
        * specified event.
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  the name of the internal procedure associated with specified event.
        */
       @Override
       public character getCallbackProcName(Text eventName)
       {
          return getCallbackProcName(eventName.toJavaType());
       }

       /**
        * Retrieves the handle of the procedure that contains the internal procedure associated
        * with the ABL callback for the specified event
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  a handle to the procedure that contains the callback procedure.
        */
       @Override
       public handle getCallbackProcContext(Text eventName)
       {
          return getCallbackProcContext(eventName.toJavaType());
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName, object<?> context)
       {
          return setCallback(callbackName.toJavaType(), routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, String routineName, handle context)
       {
          return setCallbackProcedure(callbackName, routineName, context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName, handle context)
       {
          return setCallbackProcedure(callbackName.toJavaType(), routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName, handle context)
       {
          return setCallbackProcedure(callbackName, routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, String routineName)
       {
          return setCallback(callbackName, routineName, (object<?>) null);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName)
       {
          return setCallback(callbackName.toJavaType(), routineName.toJavaType(), (object<?>) null);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName)
       {
          return setCallback(callbackName.toJavaType(), routineName);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName)
       {
          return setCallback(callbackName, routineName.toJavaType());
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName, object<?> context)
       {
          return setCallback(callbackName, routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName, object<?> context)
       {
          return setCallback(callbackName.toJavaType(), routineName, context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName, handle context)
       {
          return setCallback(callbackName.toJavaType(), routineName, context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, String proc)
       {
          return setCallbackProcedure(event.toJavaType(), proc, null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        *
        * @param   eventName
        *          The eventName of the event.
        * @param   callback
        *          The eventName of the internal procedure associated with the callback event.
        * @param   context
        *          A handle to a procedure that contains the internal procedure specified by
        *          {@code callback}. If not specified, {@code THIS-PROCEDURE} is used as the procedure
        *          context.
        *
        * @return  {@code true} when the method ends with success.
        */
       @Override
       public logical setCallbackProcedure(String eventName, String callback, handle context)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(11951);
             // SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset.
             return logical.FALSE;
          }

          if (!isCallbackName(eventName))
          {
             return logical.FALSE;
          }

          if (callback == null)
          {
             return logical.TRUE;
          }

          if (callback.isEmpty())
          {
             if (callbacks != null)
             {
                callbacks.remove(eventName.toUpperCase());
             }

             return logical.TRUE;
          }

          if (context == null || context.isUnknown())
          {
             // defaulting to THIS-PROCEDURE
             context = pm.thisProcedure();
          }
          else
          {
             if (!context._isValid() || !(context.getResource() instanceof PersistentProcedure))
             {
                ErrorManager.recordOrShowError(13273);
                // Third argument to SET-CALLBACK must be a valid procedure handle or object reference.
                return logical.FALSE;
             }
          }

          InvokeConfig callSite = getCallSite(eventName);
          if (callbacks == null)
          {
             callbacks = new HashMap<>();
          }
          callbacks.put(eventName.toUpperCase(), new CallbackData(callback, context, null, callSite));

          return logical.TRUE;
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are invalid.
        */
       @Override
       public logical setCallback(String callbackName, String routineName, object<?> context)
       {
          if (dataSet == null || !dataSet.valid())
          {
             ErrorManager.recordOrShowError(11951);
             // SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset.
             return logical.FALSE;
          }

          if (!isCallbackName(callbackName))
          {
             return logical.FALSE;
          }

          if (routineName == null)
          {
             return logical.TRUE;
          }

          if (routineName.isEmpty())
          {
             if (callbacks != null)
             {
                callbacks.remove(callbackName.toUpperCase());
             }
             return logical.TRUE;
          }

          CallbackData cbd;
          if (context == null || context.isUnknown())
          {
             // defaulting to THIS-OBJECT or to THIS-PROCEDURE, depending on the context
             if (ObjectOps.thisObject().isUnknown())
             {
                InvokeConfig callSite = getCallSite(callbackName);
                cbd = new CallbackData(routineName, pm.thisProcedure(), null, callSite);
             }
             else
             {
                cbd = new CallbackData(routineName, null, ObjectOps.thisObject(), null);
             }
          }
          else
          {
             cbd = new CallbackData(routineName, null, context, null);
          }

          if (callbacks == null)
          {
             callbacks = new HashMap<>();
          }
          callbacks.put(callbackName.toUpperCase(), cbd);

          return logical.TRUE;
       }

       /**
        * Loads the empty target {@code TempTable}) object with changed rows from original (source)
        * object. This can be either the BEFORE or the AFTER buffer. If the BEFORE/AFTER temp-tables
        * are already populated, they are emptied before copying the changes.
        *
        * @param   original
        *          Handle to original object {@code DataSet} buffer (BEFORE or AFTER buffer) that
        *          contains the changed rows to load into the target object.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original)
       {
          if (original == null || original.isUnknown() || !original.isType(LegacyResource.BUFFER))
          {
             ErrorManager.recordOrShowError(11947);
             // bufferhdl:GET-CHANGES must have a valid buffer handle to the original table, from which changes are coming, as the first argument.
             return logical.FALSE;
          }

          // check the argument first: it must be an AFTER or BEFORE buffer:
          BufferImpl srcBefore = (BufferImpl) original.getResource();
          BufferImpl srcAfter = null;
          BufferImpl buf = srcBefore.beforeBufferNative();
          if (buf == null)
          {
             buf = srcBefore.afterBufferNative();
             if (buf == null)
             {
                ErrorManager.recordOrShowError(11925, srcBefore.doGetName());
                // GET/MERGE-CHANGES require a BEFORE-TABLE for table <name>.
                return logical.FALSE;
             }
             else
             {
                srcAfter = buf;
             }
          }
          else
          {
             // oops, [xHandle] is the BEFORE buffer and [srcBefore] is the AFTER buffer, fixing:
             srcAfter = srcBefore;
             srcBefore = buf;
          }

          // now check the current buffer: it also must be an AFTER or BEFORE buffer:
          BufferImpl dstBefore = null;
          BufferImpl dstAfter = null;
          buf = beforeBufferNative();
          if (buf == null)
          {
             buf = afterBufferNative();
             if (buf == null)
             {
                ErrorManager.recordOrShowError(11925, doGetName());
                // GET/MERGE-CHANGES require a BEFORE-TABLE for table <name>.
                return logical.FALSE;
             }
             else
             {
                dstBefore = this;
                dstAfter = buf;
             }
          }
          else
          {
             dstBefore = buf;
             dstAfter = this;
          }

          // @this moment we have the dst/src-Before/After initialized. Be sure dsts are empty first:
          dstBefore.setUpBeforeBuffer(true);
          AbstractTempTable dstTT = (AbstractTempTable) dstAfter.tableHandleNative();
          boolean trackingChanges = dstTT._isTrackingChanges();
          dstTT.setTrackingChanges(false);
          try
          {
             if (!dstBefore.deleteAll().getValue() || !dstAfter.deleteAll().getValue())
             {
                return logical.FALSE;
             }
          }
          finally
          {
             // restore back
             if (trackingChanges)
             {
                dstTT.setTrackingChanges(true);
             }
             dstBefore.setUpBeforeBuffer(false);
          }

          boolean ok = TemporaryBuffer.copyChanges((TemporaryBuffer) srcBefore.buffer(),
                                                   (TemporaryBuffer) srcAfter.buffer(),
                                                   (TemporaryBuffer) dstBefore.buffer(),
                                                   (TemporaryBuffer) dstAfter.buffer());

          // in 4GL, there is no guarantee as to the contents of the buffers involved, when the method returns
          // to add predictability, FWD makes sure they are empty
          srcBefore.release();
          srcAfter.release();

          if (ok)
          {
             TempTable origin = TempTableBuilder.asTempTable((Temporary) srcAfter);
             ((AbstractTempTable) TempTableBuilder.asTempTable((Temporary) dstBefore)).setOriginTable(origin);
             ((AbstractTempTable) TempTableBuilder.asTempTable((Temporary) dstAfter)).setOriginTable(origin);
          }
          return logical.of(ok);
       }

       /**
        * Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
        * original (source) object.
        *
        * @param   original
        *          Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
        *          changed rows to load into the target object.
        * @param   parentMode
        *          When {@code TRUE} the target gets the changed parent rows of each changed child row.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original, logical parentMode)
       {
          return getChanges(original, parentMode.getValue());
       }

       /**
        * Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
        * original (source) object.
        *
        * @param   original
        *          Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
        *          changed rows to load into the target object.
        * @param   parentMode
        *          When {@code TRUE} the target gets the changed parent rows of each changed child row.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original, boolean parentMode)
       {
          ErrorManager.recordOrThrowError(12778);
          // GET-PARENT mode not supported for buffer GET-CHANGES.
          return logical.FALSE;
       }

       /**
        * Merges single changed row of a single temp-table in a source {@code DataSet} object loaded
        * with the {@code GET-CHANGES()} method into the corresponding rows of a single temp-table
        * into the original (target) {@code DataSet} buffer.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeRowChanges()
       {
          return mergeRowChanges(null, false);
       }

       /**
        * Merges single changed row of a single temp-table in a source {@code DataSet} object loaded
        * with the {@code GET-CHANGES()} method into the corresponding rows of a single temp-table
        * into the original (target) {@code DataSet} buffer.
        *
        * @param   original
        *          A handle to the original (target) buffer object of a single target {@code DataSet}
        *          temp-table to merge with the changed rows from the source object.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeRowChanges(handle original)
       {
          return mergeRowChanges(original, false);
       }

       /**
        * Merges single changed row of a single temp-table in a source {@code DataSet} object loaded
        * with the {@code GET-CHANGES()} method into the corresponding rows of a single temp-table
        * into the original (target) {@code DataSet} buffer.
        *
        * @param   original
        *          A handle to the original (target) buffer object of a single target {@code DataSet}
        *          temp-table to merge with the changed rows from the source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeRowChanges(handle original, boolean allCopyMode)
       {
          if (!_available())
          {
             ErrorManager.recordOrShowError(11937, doGetName());
             // MERGE-ROW-CHANGES buffer <name> not available.
             return logical.FALSE;
          }

          BufferImpl afterBufferChanges = null;
          if (!isBeforeBuffer())
          {
             if (!isAfterBuffer())
             {
                ErrorManager.recordOrShowError(11925, doGetName());
                // GET/MERGE-CHANGES require a BEFORE-TABLE for table <name>.
                return logical.FALSE;
             }
             else
             {
                afterBufferChanges = this;
             }
          }
          else
          {
             afterBufferChanges = peerBuffer;
          }

          BufferImpl origBuffer;
          AbstractTempTable originTable;
          if (original == null)
          {
             TempTable parentTable = this.buffer().getParentTable();
             originTable = (AbstractTempTable) parentTable.getOriginTable();
             if (originTable == null || !originTable.valid())
             {
                ErrorManager.recordOrShowError(11944, afterBufferChanges.doGetName());
                // MERGE-ROW-CHANGES finds ORIGIN-HANDLE in table <name> is no longer valid.
                return logical.FALSE;
             }
             origBuffer = (BufferImpl) originTable.defaultBufferHandleNative();
          }
          else
          {
             if (!original._isValid() || !original.isType(LegacyResource.BUFFER))
             {
                ErrorManager.recordOrShowError(11943);
                // First argument to MERGE-ROW-CHANGES must be a valid BUFFER handle.
                return logical.FALSE;
             }
             origBuffer = (BufferImpl) original.getResource();
             originTable = (AbstractTempTable) origBuffer.tableHandleNative();
          }

          if (originTable._isTrackingChanges())
          {
             ErrorManager.recordOrShowError(11922, originTable.name().toStringMessage());
             // MERGE-CHANGES may not be run on table <name> with TRACKING-CHANGES on.
             return logical.FALSE;
          }

          if (origBuffer.beforeBufferType == BeforeType.BEFORE)
          {
             // the mergeRowChangesImpl() requires the AFTER-BUFFER as second parameter
             origBuffer = origBuffer.peerBuffer;
          }

          return logical.of(mergeRowChangesImpl(this.ref(), origBuffer.ref()));
       }

       /**
        * Merges single changed row of a single temp-table in a source {@code DataSet} object loaded
        * with the {@code GET-CHANGES()} method into the corresponding rows of a single temp-table
        * into the original (target) {@code DataSet} buffer.
        *
        * @param   original
        *          A handle to the original (target) buffer object of a single target {@code DataSet}
        *          temp-table to merge with the changed rows from the source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeRowChanges(handle original, logical allCopyMode)
       {
          return mergeRowChanges(original, !allCopyMode.isUnknown() && allCopyMode.getValue());
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source
        * {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
        * rows of all temp-tables or a single temp-table (respectively) in the original (target)
        * {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a
        *          single target {@code DataSet} temp-table to merge with the changed rows from the
        *          source object.
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original)
       {
          return mergeChanges(original, false);
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source
        * {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
        * rows of all temp-tables or a single temp-table (respectively) in the original (target)
        * {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a
        *          single target {@code DataSet} temp-table to merge with the changed rows from the
        *          source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original, logical allCopyMode)
       {
          return mergeChanges(original, !allCopyMode.isUnknown() && allCopyMode.getValue());
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source {@code DataSet} object
        * loaded with the {@code GET-CHANGES()} method into the corresponding rows of all temp-tables or a single
        * temp-table (respectively) in the original (target) {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a single
        *          target {@code DataSet} temp-table to merge with the changed rows from the source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be merged.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original, boolean allCopyMode)
       {
          // the parameter must be a valid dataset handle:
          if (original == null || original.isUnknown() || !original.isType(LegacyResource.BUFFER))
          {
             ErrorManager.recordOrShowError(11953);
             // First argument to MERGE-CHANGES must be a valid BUFFER handle.
             return logical.FALSE;
          }

          BufferImpl origBuf = (BufferImpl) original.getResource();
          origBuf = origBuf.ref();
          AbstractTempTable origTT = (AbstractTempTable) tableHandleNative().getOriginTable();
          if (origTT != origBuf.tableHandleNative())
          {
             ErrorManager.recordOrShowError(11923, origBuf.doGetName(), doGetName());
             // MERGE-CHANGES run on original table <name> that does not match the ORIGIN-HANDLE in the GET-CHANGES table <name>.
             return logical.FALSE;
          }

          if (origTT._isTrackingChanges())
          {
             ErrorManager.recordOrShowError(11922, origTT.name().toStringMessage());
             // MERGE-CHANGES may not be run on table <name> with TRACKING-CHANGES on.
             return logical.FALSE;
          }

          // make sure we use the AFTER-TABLE buffer
          if (origBuf.isBeforeBuffer())
          {
             origBuf = origBuf.peerBuffer;
          }

          if (isAfterBuffer())
          {
             return logical.of(mergeChangesImpl(origBuf, allCopyMode));
          }
          else if (isBeforeBuffer())
          {
             return logical.of(peerBuffer.mergeChangesImpl(origBuf, allCopyMode));
          }

          ErrorManager.recordOrShowError(11925, doGetName());
          // GET/MERGE-CHANGES require a BEFORE-TABLE for table <name>.
          return logical.TRUE;
       }

       /**
        * Accepts changes to the data in target object (a temp-table or all temp-tables in a {@code DataSet}).
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical acceptChanges()
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11887, doGetName());
             // ACCEPT-CHANGES run on table <name> that has no BEFORE-TABLE.
             return logical.FALSE;
          }

          RecordBuffer beforeBuf = buffer();
          Persistence persistence = beforeBuf.getPersistence();
          BufferImpl after = afterBufferNative();
          RecordBuffer afterBuf = after.buffer();

          // invalidate index-based caches because of low-level operations:
          afterBuf.invalidateFFCache(0);
          beforeBuf.invalidateFFCache(0);

          // release all buffers
          ((TemporaryBuffer) afterBuf).getAllSlaveBuffers().forEach((b) -> b.release(false));

          // execute a simple query to update the [_rowState], [_originRowid] and [_peerRowid] fields to default
          // values in the AFTER-BUFFER. The [_dataSourceRowid] remains unmodified.
          StringBuilder buf = new StringBuilder();
          buf.append("update ").append(afterBuf.getTable()).append(" set ")
             .append(Buffer.ROW_STATE_FIELD).append("=").append(Buffer.ROW_UNMODIFIED).append(", ")
             .append(Buffer.ORIGIN_ROWID_FIELD).append("=NULL, ")
             .append(Buffer.AFTER_ROWID_FIELD).append("=NULL")
             .append(" where ")
             .append(TemporaryBuffer.MULTIPLEX_FIELD_NAME).append(" = ?");

          Object[] args = new Object[] {afterBuf.getMultiplexID()};
          try
          {
             boolean noUndo = !beforeBuf.isUndoable();
             int cnt = persistence.executeSQL(buf.toString(), Persistence.TEMP_CTX, args, noUndo);
             afterBuf.updateCachedRecords((BaseRecord r) ->
             {
                // direct access, so the row's state doesn't get changed
                // at this moment, all buffers are flushed, nothing is new/deleted/dirty
                OrmUtils.setField(r, TempRecord._ROW_STATE_DATA_INDEX, Buffer.ROW_UNMODIFIED);
                OrmUtils.setField(r, TempRecord._ORIGIN_ROWID_DATA_INDEX, null);
                OrmUtils.setField(r, TempRecord._PEER_ROWID_DATA_INDEX, null);
             });
             if (LOG.isLoggable(Level.FINE))
             {
                LOG.log(Level.FINE, "ACCEPT-CHANGES accepted " + cnt + " changes from " + beforeBuf.getDMOName());
             }
          }
          catch (PersistenceException e)
          {
             return logical.FALSE;
          }

          // then drop the before-image data for this table:
          try
          {
             beforeBuf.delete(TemporaryBuffer.MULTIPLEX_FIELD_NAME + "=?", beforeBuf.getMultiplexID());
          }
          catch (PersistenceException e)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING,
                        "ACCEPT-CHANGES: Failed to drop before images of " + afterBuf.getDMOName());
             }
          }

          beforeBuf.pruneSessionCache();

          afterBuf.getParentTable().setOriginTable(null);
          beforeBuf.getParentTable().setOriginTable(null);

          return logical.TRUE;
       }

       /**
        * Rejects changes to the data in target object (a temp-table or all temp-tables in a
        * {@code DataSet}).
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical rejectChanges()
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11919, doGetName());
             // REJECT-CHANGES run on table <name> that has no BEFORE-TABLE.
             return logical.FALSE;
          }

          TemporaryBuffer buffer = (TemporaryBuffer) buffer();
          try
          {
             buffer.flush();
          }
          catch (ValidationException e)
          {
             // normally this should not happen because before tables do not have unique indexes
             // TODO: but if it still happens? which error we we throw?
             return logical.FALSE;
          }
          return logical.of(buffer.rejectChanges(afterBufferNative()));
       }

       /**
        * Directly set the {@link #name()} attribute.
        *
        * @param    name
        *           The name to be set.
        */
       protected void nameInternal(character name)
       {
          this.name = name.getValue();
       }

       /**
        * Tests whether the table from this buffer has a legacy field named {@code legFieldName}.
        *
        * @param   legFieldName
        *          The legacy field name to be tested.
        *
        * @return  {@code true} if the buffer has a legacy field as specified.
        */
       protected boolean hasField(String legFieldName)
       {
          return buffer().getDmoInfo().byLegacyName(legFieldName) != null;
       }

       /**
        * Tests whether the table from this buffer has a legacy field named {@code legFieldName}.
        * Unlike the {@code #hasField} method, this method will validate the field names that also
        * contain the buffer name as qualification.
        *
        * @param   legFieldName
        *          The field name to be tested.
        *
        * @return  {@code true} if the buffer has a legacy field as specified.
        */
       protected boolean hasFieldEx(String legFieldName)
       {
          // normalize name (redundant with DmoMeta.byLegacyName below, but needed for pre-processing)
          legFieldName = TextOps.rightTrimLower(legFieldName);

          // check whether this is a scoped name
          if (legFieldName.contains("."))
          {
             String prefix = (name == null ? "?" : name) + ".";
             if (StringHelper.startsWithIgnoreCase(legFieldName, prefix))
             {
                legFieldName = legFieldName.substring(prefix.length());
             }
             else
             {
                return false; // not this buffer
             }
          }

          if (legFieldName.equals(Session.PK))
          {
             return true;
          }

          return buffer().getDmoInfo().byLegacyName(legFieldName) != null;
       }

       /**
        * Actual setting of the query. This is called indirectly, by FWD from the query when this
        * buffer is set / added.
        *
        * @param   qry
        *          The query to associate.
        */
       public void setQuery(QueryWrapper qry)
       {
          if (qry == null || !qry.valid())
          {
             this.lastQuery = null;
             return;
          }

          this.lastQuery = qry;
       }

       /**
        * Reset the {@link #lastQuery} if is the same as the given one, as the query has been deleted.
        *
        * @param   qry
        *          The qry to check against.
        */
       protected void resetLastQuery(QueryWrapper qry)
       {
          if (lastQuery == qry)
          {
             this.lastQuery = null;
          }
       }

       /**
        * Sets the current 'parent' dataset. A buffer can be in at most one dataset at a specific
        * time. Trying to add it to another one will generate error 11860. This is checked in
        * dataset add... procedures.
        *
        * @param   ds
        *          The new parent {@code DataSet}.
        */
       protected void setDataSet(DataSet ds)
       {
          if (ds != null && this.dataSet != null && this.dataSet.valid())
          {
             LOG.warning("New dataset configured but one is already active.");
             // return internal error ?
          }

          detachDataSourceImpl();
          dataSet = (ds == null) ? null : ds.ref();
          lastBatch = false;
       }

       /**
        * Flags the bracketing internal management of BEFORE-BUFFER. ABL programmer's direct
        * actions (CREATE/DELETE/ALTER buffer) are outside this bracket and will lead to errors.
        * The flag is forwarded to {@code RecordBuffer}.
        *
        * @param   on
        *          {@code true} to activate bracket, {@code false} to end it and activate errors.
        */
       public void setUpBeforeBuffer(boolean on)
       {
          this.setUpBeforeBuffer = on;
       }

       /**
        * Checks whether we are out of BEFORE-TABLE management mode. In this mode, changes to the
        * record in buffer, including CREATE/DELETE <strong>WILL</strong> throw errors.
        *
        * @return  {@code false} when {@link #setUpBeforeBuffer} is set.
        */
       public boolean hasBeforeBufferConstraints()
       {
          return !setUpBeforeBuffer;
       }

       /**
        * Tests whether this is a {@code BEFORE-BUFFER} of a dataset.
        *
        * @return  {@code true} if this is a {@code BEFORE-BUFFER} of a dataset.
        */
       public boolean isBeforeBuffer()
       {
          if (beforeBufferType == BeforeType.UNKNOWN)
          {
             computeBufferType();
          }

          return beforeBufferType == BeforeType.BEFORE;
       }

       /**
        * Tests whether this is a {@code AFTER-BUFFER} of a dataset.
        *
        * @return  {@code true} if this is a {@code AFTER-BUFFER} of a dataset.
        */
       public boolean isAfterBuffer()
       {
          if (beforeBufferType == BeforeType.UNKNOWN)
          {
             computeBufferType();
          }

          return beforeBufferType == BeforeType.AFTER;
       }

       /**
        * Constructs and returns a string containing the P4GL schema definition of the table. The result is not
        * cached so this method is to be used only for debug.
        *
        * @return  a string containing the P4GL legacy schema definition of the table.
        */
       public String tableDefinition()
       {
          return buffer().getDmoInfo().getTableDefinition();
       }

       /**
        * Computes the buffer type and if it is an {@code AFTER} or a {@code BEFORE} the
        * peer (complementary) buffer is also stored in {@code peerBuffer}.
        */
       void computeBufferType()
       {
          if (!(this instanceof Temporary))
          {
             peerBuffer = null;
             beforeBufferType = BeforeType.SIMPLE;
             return;
          }

          TempTable tempTable = TempTableBuilder.asTempTable((Temporary) this);

          TempTable ttBefore = tempTable.getBeforeTableNative();
          if (ttBefore != null)
          {
             peerBuffer = (BufferImpl) ttBefore.defaultBufferHandleNative();
             peerBuffer = peerBuffer.ref();
             beforeBufferType = BeforeType.AFTER;
             return;
          }

          TempTable ttAfter = tempTable.getAfterTableNative();
          if (ttAfter != null)
          {
             peerBuffer = (BufferImpl) ttAfter.defaultBufferHandleNative();
             peerBuffer = peerBuffer.ref();
             beforeBufferType = BeforeType.BEFORE;
             return;
          }

          peerBuffer = null;
          beforeBufferType = BeforeType.SIMPLE;
       }

       /**
        * Invokes a defined callback if one exist.
        *
        * @param   event
        *          Event name.
        *
        * @return  {@code false} if the callback raised an error. If the callback does not exist or ended with
        *          success {@code true} is returned.
        */
       boolean invokeCallback(String event)
       {
          if (dataSet == null)
          {
             return true;
          }

          CallbackData cbd = callbacks == null ? null : callbacks.get(event);
          if (cbd == null)
          {
             return true;
          }

          return dataSet.invokeCallback(new handle(this), cbd);
       }

       /**
        * Set NAME attribute without any checks.
        *
        * @param name
        *        Name to set.
        */
       protected void doSetName(String name)
       {
          this.name = name;
       }

       /**
        * Get the {@code NAME} attribute as java string.
        *
        * @return  the {@code NAME} attribute as a java string. {@code null} if {@code NAME} is {@code unknown}.
        */
       public String doGetName()
       {
          return this.name;
       }

       /**
        * Makes a backup of the current name and it replaces it with another one.
        * This is usually used by an argument proxy to temporarily replace the name.
        * The override is successful only if the name wasn't overridden beforehand.
        *
        * @param     newName
        *            The new name which should be temporarily used by this Buffer.
        *
        * @return    true if the overriding was successful, false otherwise.
        */
       protected boolean overrideName(String newName)
       {
          if (oldName != null)
          {
             return false;
          }

          oldName = name;
          name = newName;
          return true;
       }

       /**
        * Restores the name by replacing the current name with the one stored in the backup.
        */
       protected void restoreName()
       {
          name = oldName;
          oldName = null;
       }

       /**
        * Check if this resource supports the NEXT-SIBLING attribute. Since the session Buffer list
        * is a simple linked list, the returned value is {@code true}. The chain is one-directional,
        * starting at SESSION:FIRST-BUFFER.
        * <p>
        * Only the {@link #dynamic} buffers are stored in the chain. The method will return false for
        * static buffers.
        *
        * @return   {@code true} only for dynamic buffers.
        */
       @Override
       protected boolean hasNextSibling()
       {
          return buffer() != null && _dynamic();
       }

       /**
        * Check if this resource supports the PREV-SIBLING attribute. Since the session Buffer list
        * is only a simple linked list, the returned value is {@code false}. The buffers chain is
        * one-directional, starting at SESSION:FIRST-BUFFER. There is no LAST-BUFFER attribute
        * associated with the SESSION handle.
        *
        * @return   Always {@code false}.
        */
       @Override
       protected boolean hasPrevSibling()
       {
          return false;
       }

       /**
        * Interlinks the newly created HandleChain in the both chains. The back and forth links are
        * created with respective elements.
        * The heads of the chains are updated: if this is the first of its kind, it becomes the first
        * element of the chain. It is always stored as the last resource in his chain.
        * Even if this kind of resource does not support NEXT or PREV sibling, both heads of the
        * chain are kept for a fast access in future maintenance.
        * <p>
        * BUFFERs use special order: TEMP-TABLES (static and dynamic in the order they are added)
        * first, then PERMANENT buffers at the end of the list.
        */
       @Override
       protected void interlink()
       {
          if (!hasNextSibling() && !hasPrevSibling())
          {
             // quick out this resource is not chain-able
             return;
          }

          String type = type();
          boolean permanentTable = this.tableHandleNative() == null;

          BufferImpl firstPermanent = (BufferImpl) HandleChain.getHead(PERMANENT_TABLE);
          if (permanentTable)
          {
             // add buffer at the end, like other resources, but don't forget to update the
             // first-permanent-buffer pointer:
             if (firstPermanent == null)
             {
                // there are no others, this is the first permanent buffer
                HandleChain.setHead(PERMANENT_TABLE, this);
             }
             // then we continue processing like any other resource,
             // appending this resource at the end of the list
          }
          else
          {
             // we must insert this TEMP buffer just before the FIRST-PERMANENT pointer
             if (firstPermanent != null)
             {
                if (firstPermanent.prevSibling == null)
                {
                   // there are no TEMP-TABLE buffers? then install in front
                   HandleChain.setHead(type, this);
                   firstPermanent.prevSibling = this;
                   this.nextSibling = firstPermanent;
                   this.prevSibling = null;
                }
                else
                {
                   // there are both TEMP and PERMANENT TABLE buffers, insert in between
                   this.nextSibling = firstPermanent;
                   this.prevSibling = firstPermanent.prevSibling;
                   ((BufferImpl)this.nextSibling).prevSibling = this;
                   ((BufferImpl)this.prevSibling).nextSibling = this;
                }
                return; // all processing done here
             }
             // else there are no other permanent buffers, continue processing like any other
             // resource, appending this resource at the end of the list
          }

          super.interlink();
       }

       /**
        * Check if the resource can veto the delete or not, before attempting the {@link #delete()} call.
        *
        * @param    ref
        *           The exact handle holding the reference, on which the delete is attempted.
        * @param    explicit
        *           Flag indicating this is called via <code>DELETE OBJECT</code> from the application.
        *
        * @return   <code>false</code> if the buffer's {@link #_dataSet() DATASET} is registered as a OUTPUT
        *           DATASET-HANDLE parameter.
        */
       @Override
       public boolean allowDelete(handle ref, boolean explicit)
       {
          DataSet bufds = _dataSet();
          if (bufds != null && bufds.valid())
          {
             DataSetManager dmgr = DataSetManager.instance();

             if (dmgr.isOutputDataSetHandle(bufds))
             {
                // TODO: when (and if) will we delete the buffer?
                return false;
             }
             else if (explicit && _dynamic())
             {
                ErrorManager.recordOrThrowError(12326,
                                                "Cannot delete temp-table member " + _name() +
                                                " of dataset  without deleting the dataset first. (12326)",
                                                false,
                                                true);
                return false;
             }
          }
          /*
          CA: this code is disabled because 'isOutputTableHandle' checks for reference equality, which
              is always false, as 'tableHandle()' always returns a new handle
          handle htt = tableHandle();
          if (htt._isValid()                                &&
              htt.getResource() instanceof TempTableBuilder &&
              BufferManager.get().isOutputTableHandle(htt))
          {
             // TODO: when (and if) will we delete the buffer?
             return false;
          }
          */

          return true;
       }

       /**
        * Delete the resource.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             return RESOURCE_DEL.timerWithReturn(this::resourceDeleteImpl);
          }
          else
          {
             return resourceDeleteImpl();
          }
       }

       /**
        * Delete the resource.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       private boolean resourceDeleteImpl()
       {
          if (!_dynamic() && !implicitDeletion())
          {
             // static buffers can't be deleted, but no error message is shown
             return false;
          }

          RecordBuffer recBuf = buffer();
          boolean staticTT = recBuf.isTemporary() && !recBuf.isDynamic();
          if (staticTT)
          {
             TempTable tt = tableHandleNative();
             if (tt != null && tt.valid())
             {
                // if this is the default buffer for a GLOBAL SHARED TEMP-TABLE, then do not delete
                if (recBuf.isGlobal() && tt.defaultBufferHandleNative() == this)
                {
                   return false;
                }

                // if there are still other buffers bounded to it, then do not delete.
                TemporaryBuffer tbuf = (TemporaryBuffer) recBuf;
                if (tbuf.getMasterBuffer() == tbuf && tbuf.hasExplicitBuffers())
                {
                   return false;
                }
             }
          }

          deleting = true;
          // the order must be preserved: there are checks  in resourceDeleted for the buffer's open scope count,
          // which gets decremented in 'finished'.
          recBuf.resourceDeleted();
          recBuf.finished();

          SharedVariableManager.cleanupBuffer(recBuf.getDMOProxy());

          deleted = true;
          deleting = false;

          if (staticTT)
          {
             TemporaryBuffer tbuf = (TemporaryBuffer) recBuf;
             TemporaryBuffer mbuf = (TemporaryBuffer) tbuf.getMasterBuffer();
             if (!mbuf.isGlobal() && mbuf != tbuf && !mbuf.hasExplicitBuffers())
             {
                TempTable tt = mbuf.getParentTable();
                if (tt != null && tt.valid() && !tt.instantiatingProcedure()._isValid())
                {
                   // if the master table is referenced only from other programs and its program has been deleted,
                   // and this is the last child buffer referencing it, then delete it, too.
                   StaticTempTable stt = (StaticTempTable) tt;
                   stt.setForceDelete(true);

                   pm.deleteResource(stt);
                }
             }
          }

          if (fields != null)
          {
             for (int i = 0; i < fields.length; i++)
             {
                if (fields[i] != null)
                {
                   fields[i].forceDelete();
                }
             }
          }

          fieldHandles = null;

          return true;
       }

       /**
        * Check whether an extent field index is within the valid range {@code 0 &le; index &lt; max}.
        * Raise an error if not, unless errors are silenced.
        * <p>
        * This method is meant to be called by DMO proxy objects which extend this class, from
        * within their indexed setter and getter methods which accept an {@code int} value for the
        * element index.
        *
        * @param   index
        *          Zero-based extent field index to check.
        * @param   max
        *          Maximum number of elements in the extent field.
        *
        * @return  {@code true} if {@code index} falls within the valid range,
        *          {@code false} if outside the range and errors are silenced.
        *
        * @throws  ErrorConditionException
        *          if {@code index} is outside the valid range and errors are not silenced.
        */
       public boolean checkExtentRange(int index, int max)
       {
          if (index < 0 || index >= max)
          {
             outOfBoundsError(index);

             return false;
          }

          return true;
       }

       /**
        * Record or throw an "array subscript out of range" error condition for the given subscript.
        *
        * @param   index
        *          Zero-based subscript value.
        */
       protected void outOfBoundsError(int index)
       {
          String msg = "Array subscript " + (index + 1) + " is out of range";
          ErrorManager.recordOrThrowError(26, msg);
       }

       /**
        * Delete the record currently in the buffer, if any.
        *
        * @param   validated
        *          <code>true</code> if there was validation and it passed. <code>false</code> if
        *          there was no validation.
        *
        * @return  {@code true} if the delete was possible.
        *
        * @throws  ErrorConditionException
        *          if no record currently is loaded in the buffer, or if there is
        *          an error deleting the current record from the database.
        */
       protected logical deleteRecord(boolean validated)
       {
          RecordBuffer recBuf = buffer();

          if (!recBuf.isTemporary())
          {
             if (!verifyTransaction(recBuf))
             {
                return logical.FALSE;
             }

             if (!validated)
             {
                Class<? extends DataModelObject> iface = recBuf.getDMOInterface();
                if (TableMapper.hasValidation(iface))
                {
                   String msg = "Table " + recBuf.getDmoInfo().legacyTable + " is defined with schema delete " +
                                "validation and may only be deleted by the DELETE statement";
                   ErrorManager.recordOrShowError(7347, msg, false, false);

                   return logical.FALSE;
                }
             }
          }

          if (dataSet != null) // implies buffer.isTemporary()
          {
             // checks whether tracking is active
             if (recBuf.getParentTable()._isTrackingChanges())
             {
                if (!invokeCallback(ROW_DELETE_EVENT))
                {
                   returnError(); // veto on record delete
                }
             }
          }

          try
          {
             recBuf.delete();
          }
          catch (PersistenceException exc)
          {
             return logical.FALSE;
          }

          return logical.TRUE;
       }

       /**
        * Test whether a string is a valid CALLBACK name for {@code DataSet}s. This method is
        * responsible for raising the 11950 error if the name specified is unknown or nor not a name
        * of a known callback for {@code DataSet}.
        *
        * @param   someName
        *          The name to test.
        *
        * @return  {@code true} only if {@code someName} is equals to "BEFORE-FILL", "AFTER-FILL",
        *          "BEFORE-ROW-FILL", "AFTER-ROW-FILL", "ROW-CREATE", "ROW-DELETE", "ROW-UPDATE",
        *          "FIND-FAILED", or "SYNCHRONIZE", in ignore-case mode.
        */
       private static boolean isCallbackName(String someName)
       {
          if (someName == null ||
             !BEFORE_FILL_EVENT.equalsIgnoreCase(someName)     &&
             !AFTER_FILL_EVENT.equalsIgnoreCase(someName)      &&
             !BEFORE_ROW_FILL_EVENT.equalsIgnoreCase(someName) &&
             !AFTER_ROW_FILL_EVENT.equalsIgnoreCase(someName)  &&
             !ROW_CREATE_EVENT.equalsIgnoreCase(someName)      &&
             !ROW_DELETE_EVENT.equalsIgnoreCase(someName)      &&
             !ROW_UPDATE_EVENT.equalsIgnoreCase(someName)      &&
             !FIND_FAILED_EVENT.equalsIgnoreCase(someName)     &&
             !SYNCHRONIZE_EVENT.equalsIgnoreCase(someName))
          {
             ErrorManager.recordOrShowError(11950, someName != null ? someName : "");
             // Argument  to CALLBACK method must be a valid buffer or dataset event name.
             return false;
          }

          return true;
       }

       /**
        * Get the {@link InvokeConfig callsite} for the specified event.
        *
        * @param    eventName
        *           The event name.
        */
       private static InvokeConfig getCallSite(String eventName)
       {
          switch (eventName.toUpperCase())
          {
             case BEFORE_FILL_EVENT:
                return BEFORE_FILL_CALL_SITE;
             case AFTER_FILL_EVENT:
                return AFTER_FILL_CALL_SITE;
             case BEFORE_ROW_FILL_EVENT:
                return BEFORE_ROW_FILL_CALL_SITE;
             case AFTER_ROW_FILL_EVENT:
                return AFTER_ROW_FILL_CALL_SITE;
             case ROW_CREATE_EVENT:
                return ROW_CREATE_CALL_SITE;
             case ROW_DELETE_EVENT:
                return ROW_DELETE_CALL_SITE;
             case ROW_UPDATE_EVENT:
                return ROW_UPDATE_CALL_SITE;
             case FIND_FAILED_EVENT:
                return FIND_FAILED_CALL_SITE;
             case SYNCHRONIZE_EVENT:
                return SYNCHRONIZE_CALL_SITE;
          }

          return null;
       }

       /**
        * Return {@code LockType} instance for the given internal lock and nowait with corresponding
        * error processing.
        *
        * @param   lock
        *          Integer value.
        * @param   nowait
        *          Integer value.
        *
        * @return  {@code LockType} instance.
        *
        * @throws  ErrorConditionException
        *          If locking operation fails.
        */
       private static LockType fromLockNowait(long lock, long nowait)
       throws ErrorConditionException
       {
          LockType lockType = null;
          try
          {
             lockType = LockType.fromLockNowait(lock, nowait);
          }
          catch (LockTypeException e)
          {
             String err;
             int num;
             if (e.isNowaitCorrect())
             {
                err = "FIND-BY-ROWID, FIND-FIRST etc., second argument must be " +
                      "NO-LOCK, SHARE-LOCK or EXCLUSIVE-LOCK";
                num = 7344;
             }
             else
             {
                err = "FIND method argument must be NO-WAIT, or 0 or ?";
                num = 7343;
             }
             ErrorManager.recordOrThrowError(num, err);
          }
          return lockType;
       }

       /**
        * Implementation of the SERIALIZE-ROW method. Writes current row from this buffer into the specified data
        * target, using the specified format.
        *
        * @param   format
        *          The format ("xml" or "json").
        * @param   targetData
        *          Data target.
        * @param   formatted
        *          {@code True} to format the data for making it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitial
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuter
        *          {@code True} to omit data for the outermost object from the output.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       private boolean serializeRowImpl(String format,
                                        TargetData targetData,
                                        Boolean formatted,
                                        String encoding,
                                        Boolean omitInitial,
                                        Boolean omitOuter)
       {
          try
          {
             if (!buffer().isTemporary())
             {
                ErrorManager.recordOrShowError(13158);
                // XML Methods only allowed on TEMP-TABLE buffers.
                return false;
             }

             // the buffer must not be empty
             if (!_available())
             {
                ErrorManager.recordOrShowError(16017);
                // No record in buffer for SERIALIZE-ROW.
                return false;
             }

             // check the specified target format
             if (format == null)
             {
                ErrorManager.recordOrShowError(16019, "unknown", "");
                // Invalid target-format '<format>' for SERIALIZE-ROW.
                return false;
             }
             boolean isXML = "XML".equalsIgnoreCase(format);
             boolean isJSON = "JSON".equalsIgnoreCase(format);
             if (!isJSON && !isXML)
             {
                ErrorManager.recordOrShowError(16019, format, "");
                // Invalid target-format '<format>' for SERIALIZE-ROW.
                return false;
             }

             if (formatted == null)
             {
                formatted = false;
             }
             if (omitInitial == null)
             {
                omitInitial = false;
             }
             if (omitOuter == null)
             {
                omitOuter = false;
             }

             if (isXML)
             {
                if (omitOuter)
                {
                   ErrorManager.recordOrShowError(16020);
                   // omit-outer-object argument not allowed in SERIALIZE-ROW for XML
                   ErrorManager.recordOrShowError(4065, false, "SERIALIZE-ROW", "BUFFER widget");
                   // **The <attribute> attribute on the <widget id> has invalid arguments.
                   return false;
                }
                XmlExport export = new XmlExport(targetData, formatted, encoding);
                return export.writeRecord(this, omitInitial);
             }
             else
             {
                // is JSON
                com.fasterxml.jackson.core.JsonEncoding jsonEnc =
                      JsonExport.parseEncoding(new character(encoding));
                if (jsonEnc == null)
                {
                   ErrorManager.recordOrShowError(15342, encoding, "WRITE-JSON");
                   // Invalid encoding '<encoding>' argument for <method>.
                   return false;
                }

                JsonExport export = new JsonExport();
                return export.exportRecord(this, targetData, formatted, jsonEnc, omitInitial, omitOuter);
             }
          }
          finally
          {
             try
             {
                targetData.close();
             }
             catch (IOException e)
             {
                // TODO: log
             }
          }
       }

       /**
        * Obtain the list of data-source keys of this buffer. If this buffer is not in a DATA-SOURCE, {@code null}
        * is returned.
        *
        * @return  the list of data-source keys of this buffer.
        */
       List<String> keysList()
       {
          return keys;
       }

       /**
        * Sets the {@code keys} for this source buffer, related to the specified {@code DataSource}. If a
        * DATA-SOURCE was already configured using the buffer, an error message will be thrown back.
        *
        * @param   parent
        *          The parent {@code DataSource}.
        * @param   keys
        *          The array of fields used as keys.
        *
        * @return  {@code true} only if operation is successful.
        */
       boolean keys(DataSource parent, String[] keys)
       {
          if (parentDataSource != null && parentDataSource.valid())
          {
             ErrorManager.recordOrThrowError(12298, doGetName(), "");
             // Buffer <name> may be in only one data-source at a time.  Use different buffers for the same table. (12298)
             return false;
          }

          this.keys = Arrays.asList(keys);
          this.parentDataSource = parent;

          return true;
       }

       /**
        * Clear the {@link #parentDataSource} and {@link #keys}, as the {@link #parentDataSource} has been
        * deleted.
        */
       void clearParentDataSource()
       {
          this.parentDataSource = null;
          this.keys = null;
       }

       /**
        * Getter of NUM-FIELDS attribute.  Only normal fields are counted, the hidden fields are not
        * taken into consideration.
        *
        * @return  current value of NUM-FIELDS attribute.
        */
       int _numFields()
       {
          return buffer().getNumFields();
       }

       /**
        * Execute a dynamic FIRST, LAST or UNIQUE find query. All the public overloaded methods
        * (findFirst, findLast and findUnique) will call this private implementation using the
        * appropriate parameters. Internally this method uses the DynamicQueryHelper.parseFindQuery
        * static parser to return a FindQuery object using the passed predicate and locking mode.
        * After the FindQuery is obtained, the appropriate query is executed (FIRST, LAST or UNIQUE)
        * and the result is returned to caller method.
        * <p>
        * If the method fails, it does not raise an error but displays a message. To suppress it,
        * the method should be called with NO-ERROR clause.
        *
        * @param   predicate
        *          Predicate expression for find that evaluates to the following syntax:
        *             [ WHERE [ logical-expression ] ] [ USE-INDEX index-name ]
        *          <ul>
        *             <li>Once evaluated, predicate-expression can contain only constants and
        *                   unabbreviated references to fields from the buffer.
        *             <li>The predicate-expression itself can be built using a concatenation of
        *                   character expressions.
        *          </ul>
        * @param   mode
        *          Search mode, one of FIRST, LAST or UNIQUE.
        * @param   lock
        *          Lock type. May additionally have no-wait flag set.
        * @param   substBuffers
        *          A list of substitution buffers.
        *
        * @return  {@code true} if operation succeeds, else {@code false}.
        */
       logical _find(String predicate, FindMode mode, LockType lock, Buffer... substBuffers)
       {
          final String qName = "find_" + mode + "_" + lock;
          List<Buffer> substs = new ArrayList<>();
          if (substBuffers.length > 0)
          {
             substs.addAll(Arrays.asList(substBuffers));
          }
          if (parentDataSource != null)
          {
             handle hbuf = parentDataSource.getDatasetBuffer();
             if (hbuf._isValid())
             {
                BufferImpl buf = (BufferImpl) hbuf.getResource();
                if (!substs.contains(buf))
                {
                   substs.add(buf);
                }
                buf = buf.beforeBufferNative();
                if (buf != null && !substs.contains(buf))
                {
                   substs.add(buf);
                }
             }
          }
          P2JQuery query = DynamicQueryHelper.parseFindQuery(this, predicate, lock, qName, substs);
          if (query == null)
          {
             String msg = "Invalid WHERE clause in FIND method for buffer " + buffer().getLegacyName() + " (10041)";
             ErrorManager.recordOrShowError(
                   new int[] { 10041 },
                   new String[] { msg },
                   false, false, false, true, false);  // no DOT at the end of error message!
             return logical.FALSE;
          }

          boolean success = false;
          try
          {
             switch (mode)
             {
                case FIRST:
                   query.first(lock);
                   break;

                case LAST:
                   query.last(lock);
                   break;

                case UNIQUE:
                   query.unique(lock);
                   break;
             }
             if (!ErrorManager.isPendingError())
             {
                success = _available();
             }
          }
          catch (ErrorConditionException e)
          {
             // the error condition already has been handled by the ErrorManager, so we just return failure;
             // unexpected, unchecked exceptions are allowed to unwind the stack
             success = false;
          }

          return logical.of(success);
       }

       /**
        * Detaches the {@code DataSource} from the temp-table buffer in a {@code DataSet} object.
        */
       void detachDataSourceImpl()
       {
          // TODO: check if something is vetoing the detach process
          if (dataSource != null)
          {
             dataSource.attach(null, null);
             dataSource = null;
             if (isAfterBuffer())
             {
                peerBuffer.dataSource = null;
             }
          }

          attachedPairlist = null;
          dataSrcMappingStr = null;
          dataSrcMapping = null;
          dstFieldMeta = null;
       }

       /**
        * Clear the {@link #reusableQuery query} used for FILL operations.
        */
       void clearQuery()
       {
          if (reusableQuery != null)
          {
             reusableQuery.setDataRelation(null);
             reusableQuery.queryClose();
             reusableQuery = null;
          }
       }

       /**
        * Internal implementation of FIND-CURRENT() method.
        * <p>
        * Changes the lock type for the record currently in the buffer. Refreshes the record if it was
        * changed by other client. Resets the CURRENT-CHANGED attribute accordingly.
        *
        * @param   lockType
        *          Extended lock type (includes the NO-WAIT status).
        *
        * @return  {@code true} if FIND-CURRENT() succeeds (the lock type is changed), else {@code false}.
        */
       private logical _current(LockType lockType)
       {
          RecordBuffer recBuf = buffer();

          // make sure to ignore errors caused by LockUnavailableException
          try
          {
             recBuf.txHelper.errHlp.ignore(() ->
             {
                recBuf.armCurrentChanged();
                recBuf.reload(lockType, false);
             });
          }

          finally
          {
             recBuf.updateCurrentChanged();
          }

          // NOTE: the reference manual states that if lock operation fails, a message is displayed.
          //       However, this is not how the behaviour was observed. Actually there is no error
          //       whatsoever. Instead, the buffer is discarded after a failure to acquire the lock.
          //       The error was not observable, in fact at all, in all my testcases.
          return logical.of(recBuf.isAvailable());
       }

       /**
        * Implementation of the merge changes. Always to be called for the AFTER-BUFFER. It will also
        * process the changes for the BEFORE BUFFER (the DELETE changes only), too.
        *
        * @param   porigBuf
        *          The original AFTER buffer.
        * @param   allCopyMode
        *          Use {@code true} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return  {@code true} on success. When {@code false} is returned the error code was already
        *          configured in {@code ErrorManager}.
        */
       private boolean mergeChangesImpl(BufferImpl porigBuf, boolean allCopyMode)
       {
          if (!isAfterBuffer() || !porigBuf.isAfterBuffer())
          {
             ErrorManager.recordOrShowError(11925, doGetName());
             // GET/MERGE-CHANGES require a BEFORE-TABLE for table <name>.
             return false;
          }

          logical ok = new logical(true);
          TemporaryBuffer afterBuffer = (TemporaryBuffer) buffer();
          TemporaryBuffer beforeBuffer = (TemporaryBuffer) peerBuffer.buffer();
          Class<? extends Record> origAfterImplClass = porigBuf.buffer().getDmoInfo().getImplementationClass();
          Class<? extends Record> origBeforeImplClass = porigBuf.beforeBufferNative()
                .buffer().getDmoInfo().getImplementationClass();
          Class<? extends Record> afterImplClass = afterBuffer.getDmoInfo().getImplementationClass();
          Session session = afterBuffer.getPersistence().getSession(Persistence.TEMP_CTX);
          beforeBuffer.readAllRows(
             (dmo) -> {
                if (!ok.booleanValue())
                {
                   return;
                }

                // prepare the original before image
                TempRecord origBefRecord = null;
                try
                {
                   origBefRecord = (TempRecord) session.get(origBeforeImplClass, dmo._originRowid());
                }
                catch (PersistenceException | ConditionException e)
                {
                   // origBefRecord stays null
                }
                if (origBefRecord == null)
                {
                   // this should not happen. The origin-rowid of the dmo should not be unknown! If it was not
                   // found then the changes in the original table were already accepted (?)

                   ErrorManager.recordOrShowError(11928, doGetName(), porigBuf.doGetName());
                   // INVALID origin-rowid of the change table <name> of <name>.
                   ErrorManager.recordOrShowError(11926);
                   // Found unexpected error while merging changes.

                   ok.assign(false);
                   return;
                }

                Integer errFlags = dmo._errorFlags();
                if (errFlags != null && errFlags != 0)
                {
                   BufferImpl porigBefBuf = porigBuf.peerBuffer;
                   porigBefBuf.buffer().loadRecord(origBefRecord);

                   // if the record's any error/rejected markers were set, drop it from merge operation
                   ok.assign(porigBefBuf.rejectRowChanges());
                }
                else
                {
                   // prepare the original after image and put it in the [porigBuf] buffer
                   TempRecord origRecord = null;
                   try
                   {
                      origRecord = (TempRecord) session.get(origAfterImplClass, origBefRecord._peerRowid());
                   }
                   catch (PersistenceException | ConditionException e)
                   {
                      // origRecord stays null
                   }
                   porigBuf.buffer().loadRecord(origRecord);

                   // prepare the after image/record in current buffer
                   TempRecord afterRecord;
                   try
                   {
                      afterRecord = (TempRecord) session.get(afterImplClass, dmo._peerRowid());
                   }
                   catch (PersistenceException | ConditionException e)
                   {
                      afterRecord = null;
                   }
                   afterBuffer.loadRecord(afterRecord);

                   // run the merge-row-changes code for prepared records
                   ok.assign(mergeRowChangesImpl(this.ref(), porigBuf.ref()));
                }
             });

          return ok.booleanValue();
       }

       /**
        * Implementation of the row merge changes. Always to be called for the AFTER-BUFFER.
        *
        * @param   changesBuf
        *          The changes AFTER buffer.
        * @param   origAfterBuf
        *          The original AFTER buffer.
        *
        * @return  {@code true} on success. When {@code false} is returned the error code was already
        *          configured in {@code ErrorManager}.
        */
       private static boolean mergeRowChangesImpl(BufferImpl changesBuf, BufferImpl origAfterBuf)
       {
          if (changesBuf.rowState().isUnknown() || changesBuf.rowState().intValue() == Buffer.ROW_UNMODIFIED)
          {
             return true; // unmodified record, nothing to merge
          }

          int recState = changesBuf.rowState().intValue();
          boolean isError = changesBuf.error().booleanValue();

          if (recState != Buffer.ROW_DELETED)
          {
             if (changesBuf.beforeBufferType == BeforeType.AFTER && isError)
             {
                changesBuf.peerBuffer.findByRowID(changesBuf.beforeRowid());
                changesBuf = changesBuf.peerBuffer;
             }
             else if (changesBuf.beforeBufferType == BeforeType.BEFORE && !isError)
             {
                // on error keep the original (before), on non error, keep the change (after)
                changesBuf.peerBuffer.findByRowID(changesBuf.afterRowid());
                changesBuf = changesBuf.peerBuffer;
             }
          }

          TempRecord dmo = (TempRecord) changesBuf.buffer().getCurrentRecord();
          if (dmo == null)
          {
             LOG.log(Level.SEVERE, "mergeRowChangesImpl: null TempRecord in changesBuf");
             return true;
          }

          Long originB4Rowid = dmo._originRowid();
          BufferImpl origB4Buffer = origAfterBuf.peerBuffer;

          if (recState == ROW_DELETED)
          {
             if (dmo._rowState() == Buffer.ROW_DELETED &&
                 origB4Buffer.findByRowID(new rowid(originB4Rowid)).getValue())
             {
                if (origB4Buffer.rowState().intValue() != Buffer.ROW_DELETED)
                {
                   ErrorManager.recordOrShowError(11928, changesBuf.doGetName(), origAfterBuf.doGetName());
                   // INVALID origin-rowid of the change table <name> of <name>.
                   ErrorManager.recordOrShowError(11926);
                   // Found unexpected error while merging changes.
                   return false;
                }
                // just remove the BEFORE-IMAGE, the AFTER-IMAGE is long gone
                origB4Buffer.setUpBeforeBuffer(true);
                origB4Buffer.deleteRecord();
                origB4Buffer.setUpBeforeBuffer(false);
             }
             return true;
          }
          // else CREATE / MODIFIED

          if (originB4Rowid == null)
          {
             ErrorManager.recordOrShowError(11923, origAfterBuf.doGetName(), changesBuf.doGetName());
             // MERGE-CHANGES run on original table <name> that does not match the ORIGIN-HANDLE in the GET-CHANGES table <name>.
             return false;
          }
          else if (!origB4Buffer.findByRowID(new rowid(originB4Rowid)).getValue())
          {
             ErrorManager.recordOrShowError(11928, changesBuf.doGetName(), origAfterBuf.doGetName());
             // INVALID origin-rowid of the change table <name> of <name>.
             ErrorManager.recordOrShowError(11926);
             // Found unexpected error while merging changes.
             return false;
          }

          Long afterRowid = origB4Buffer.afterRowid().getValue();
          if (afterRowid == null)
          {
             ErrorManager.recordOrShowError(11926);
             // Found unexpected error while merging changes.
             return false;
             // exits lambda, continue with next record.
             // The error message will be displayed multiple times, one for each record.
          }
          else if (!origAfterBuf.findByRowID(new rowid(afterRowid)).getValue())
          {
             ErrorManager.recordOrShowError(11923, origAfterBuf.doGetName(), changesBuf.doGetName());
             // MERGE-CHANGES run on original table <name> that does not match the ORIGIN-HANDLE in the GET-CHANGES table <name>.
             return false;
             // exits lambda, continue with next record.
             // The error message will be displayed multiple times, one for each record.
          }

          RecordBuffer origAfterRecBuf = origAfterBuf.buffer();
          TempRecord dstRec = (TempRecord) origAfterRecBuf.getCurrentRecord();
          Record snapshot = null;

          if (recState == ROW_CREATED && isError)
          {
             origAfterBuf.deleteRecord(); // on error do a rollback = delete
          }
          else
          {
             if (dstRec._rowState() == null || dstRec._rowState() == Buffer.ROW_UNMODIFIED)
             {
                ErrorManager.recordOrShowError(11928, changesBuf.doGetName(), origAfterBuf.doGetName());
                // INVALID origin-rowid of the change table <name> of <name>.
                ErrorManager.recordOrShowError(11926);
                // Found unexpected error while merging changes.
                return false;
             }
             snapshot = dstRec.snapshot();

             // accepts the changes by copying the after-image table rows to the corresponding
             // after-image table rows in the original DataSet temp-table.
             BaseRecord.copy(dstRec, dmo, false);
          }

          Long oldBefore = dstRec._peerRowid();
          if (origAfterBuf.isAfterBuffer() && oldBefore != null)
          {
             // removes the before-image table row (if it has one).
             if (origB4Buffer.findByRowID(new rowid(oldBefore)).getValue())
             {
                origB4Buffer.setUpBeforeBuffer(true);
                origB4Buffer.deleteRecord();
                origB4Buffer.setUpBeforeBuffer(false);
             }
          }

          // also:
          //    sets the BEFORE-ROWID attribute of the row in the after-image table to the Unknown value (?)
          //    sets the ROW-STATE of the row in the after-image table to ROW-UNMODIFIED (0)
          //    resets ERROR/REJECTED attributes and ERROR-STRING to unknown value.
          dstRec._peerRowid(null);
          dstRec._errorFlags(null);
          dstRec._errorString(null);
          dstRec._peerRowid(null);
          dstRec._rowState(Buffer.ROW_UNMODIFIED);

          try
          {
             Map<String, List<Integer>> props = new HashMap<>();
             props.put(Buffer.ROW_STATE_FIELD, null);
             props.put(Buffer.AFTER_ROWID_FIELD, null);
             props.put(Buffer.ERROR_FLAG_FIELD, null);
             props.put(Buffer.ERROR_STRING_FIELD, null);
             origAfterRecBuf.getChangeBroker().stateChanged(
                   RecordChangeEvent.Type.UPDATE, origAfterRecBuf, dstRec, snapshot, props);
          }
          catch (PersistenceException e)
          {
             ErrorManager.recordOrShowError(11926);
             // Found unexpected error while merging changes.
             return false;
          }

          // great, return with success
          return true;
       }

       /**
        * Sets the {@code _originRowid} internal field and register the change with the {@code ChangeBroker} to
        * make sure the change is persisted.
        *
        * @param   val
        *          The new value for {@code _originRowid} field. May be {@code null} for an unknown.
        */
       private void setOriginRowidImpl(Long val)
       {
          RecordBuffer recBuf = buffer();
          TempRecord dmo = (TempRecord) recBuf.getCurrentRecord();
          TempRecord snapshot = (TempRecord) dmo.deepCopy();
          dmo._originRowid(val);
          try
          {
             Map<String, List<Integer>> props = new HashMap<>();
             props.put(Buffer.ORIGIN_ROWID_FIELD, null);
             recBuf.getChangeBroker().stateChanged(RecordChangeEvent.Type.UPDATE, recBuf, dmo, snapshot, props);
          }
          catch (PersistenceException e)
          {
             LOG.warning("Caught exception:", e);
          }
       }

       /**
        * Sets the {@code _dataSourceRowid} internal field and register the change with the {@code ChangeBroker}
        * to make sure the change is persisted.
        *
        * @param   val
        *          The new value for {@code _dataSourceRowid} field. May be {@code null} for an unknown.
        */
       private void setDatasourceRowidImpl(Long val)
       {
          RecordBuffer recBuf = buffer();
          TempRecord dmo = (TempRecord) recBuf.getCurrentRecord();
          TempRecord snapshot = (TempRecord) dmo.deepCopy();
          dmo._datasourceRowid(val);
          try
          {
             Map<String, List<Integer>> props = new HashMap<>();
             props.put(Buffer.DATA_SOURCE_ROWID_FIELD, null);
             recBuf.getChangeBroker().stateChanged(RecordChangeEvent.Type.UPDATE, recBuf, dmo, snapshot, props);
          }
          catch (PersistenceException e)
          {
             LOG.warning("Caught exception:", e);
          }
       }

       /**
        * Partial implementation of {@code SAVE-ROW-CHANGES} method. Validates some parameters and
        * calls the final worker.
        *
        * @param   bufNo
        *          The 0-base buffer index in the attached {@code DataSource}.
        * @param   skip
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        * @param   noLobs
        *          Use {@code true} to specify the BLOB and CLOB fields should be ignored in the save
        *          operation.
        *
        * @return  {@code true} if operation is successful.
        */
       private logical saveRowChangesImpl(int bufNo, String skip, boolean noLobs)
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11938, doGetName());
             // SAVE-ROW-CHANGES must be run on the BEFORE table of buffer <name>.
             return logical.FALSE;
          }

          BufferImpl after = afterBufferNative();
          DataSource dsrc = after.ref().dataSource;
          if (dsrc == null || !dsrc.valid())
          {
             ErrorManager.recordOrShowError(11942, doGetName(), after.doGetName());
             // SAVE-ROW-CHANGES for BEFORE buffer <name>, requires that the corresponding AFTER dataset member buffer <name> be attached to a DATA-SOURCE.
             return logical.FALSE;
          }

          return saveRowChangesImpl2(dsrc, after, bufNo, skip, noLobs);
       }

       /**
        * Implementation of {@code SAVE-ROW-CHANGES} method. Validates some parameters and
        * calls the final worker.
        *
        * @param   bufName
        *          The buffer name in the attached {@code DataSource}.
        * @param   skip
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        * @param   noLobs
        *          Use {@code true} to specify the BLOB and CLOB fields should be ignored in the save
        *          operation.
        *
        * @return  {@code true} if operation is successful.
        */
       private logical saveRowChangesImpl(String bufName, String skip, boolean noLobs)
       {
          if (!isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(11938, doGetName());
             // SAVE-ROW-CHANGES must be run on the BEFORE table of buffer <name>.
             return logical.FALSE;
          }

          BufferImpl after = afterBufferNative();
          DataSource dsrc = after.ref().dataSource;
          if (dsrc == null || !dsrc.valid())
          {
             ErrorManager.recordOrShowError(11942, doGetName(), after.doGetName());
             // SAVE-ROW-CHANGES for BEFORE buffer <name>, requires that the corresponding AFTER dataset member buffer <name> be attached to a DATA-SOURCE.
             return logical.FALSE;
          }

          return saveRowChangesImpl2(dsrc, after, dsrc.getSourceBufferIndex(bufName), skip, noLobs);
       }

       /**
        * Implementation of {@code SAVE-ROW-CHANGES} method.
        *
        * @param   dSrc
        *          The {@code DataSource} that will be updated.
        * @param   after
        *          The AFTER-BUFFER to which the {@code DataSource} is attached.
        * @param   bufNo
        *          The 0-base buffer index in the attached {@code DataSource}.
        * @param   skip
        *          A list of field names that should not be assigned after a new row is created
        *          (comma-separated).
        * @param   noLobs
        *          Use {@code true} to specify the BLOB and CLOB fields should be ignored in the save
        *          operation.
        *
        * @return  {@code true} if operation is successful.
        */
       private logical saveRowChangesImpl2(DataSource dSrc,
                                           BufferImpl after,
                                           int bufNo,
                                           String skip,
                                           boolean noLobs)
       {
          if (!_available())
          {
             ErrorManager.recordOrShowError(11935, false, doGetName());
             // SAVE-ROW-CHANGES buffer <name> not available.
             return logical.FALSE;
          }

          if (bufNo < 0 || bufNo >= dSrc._getNumSourceBuffers())
          {
             ErrorManager.recordOrShowError(11906, false);
             // Unable to identify data-source buffer for SAVE-ROW-CHANGES.
             return logical.FALSE;
          }

          BufferImpl srcBuff = dSrc.getSourceBufferNative(bufNo + 1);
          Set<String> skipFields = new HashSet<>();
          if (skip != null && !skip.isEmpty())
          {
             String[] skipList = skip.split(",");
             for (String tok : skipList)
             {
                if (tok.contains(".") || !srcBuff.hasField(tok))
                {
                   ErrorManager.recordOrShowError(11909, false, tok, srcBuff.doGetName());
                   // Unable to find sequence field  <name> in table <name> during SAVE-ROW-CHANGES.
                   return logical.FALSE;
                }
                skipFields.add(tok);
             }
          }

          String saveWhere = dSrc.getSaveWhereString(bufNo + 1).toJavaType();
          int rowState = rowState().intValue();
          character allPairs = new character();
          character allSrcFields = new character();

          if (rowState != ROW_DELETED)
          {
             StringBuilder sb = new StringBuilder();
             StringBuilder sbAllSrc = new StringBuilder();

             for (Pair<String, DataSource.FieldReference> entry : after.dataSrcMapping)
             {
                String srcFld = entry.getValue().getName();
                String dsFld = entry.getKey();

                if (sbAllSrc.length() != 0)
                {
                   sbAllSrc.append(",");
                }
                sbAllSrc.append(srcFld);

                if (entry.getValue().getBuffer() == srcBuff && !skipFields.contains(srcFld))
                {
                   if (sb.length() != 0)
                   {
                      sb.append(",");
                   }
                   sb.append(dsFld).append(",").append(srcFld);
                }
             }
             allPairs.assign(sb.toString());
             allSrcFields.assign(sbAllSrc.toString());
          }

          logical retval = new logical();
          OnPhrase[] onPhrase0 = new OnPhrase[]{new OnPhrase(Condition.ERROR, Action.THROW, "save-row-changes-label")};
          doBlock(TransactionType.FULL, "save-row-changes-label", onPhrase0, new Block() {
             @Override
             public void body()
             {
                if (rowState == ROW_CREATED)
                {
                   srcBuff.create();

                   after.findByRowID(afterRowid());
                   logical ok = TemporaryBuffer.copy(
                         srcBuff.buffer(), after.buffer(), allSrcFields, allPairs, logical.of(noLobs));

                   if (ok.getValue())
                   {
                      after.setDatasourceRowidImpl(srcBuff.rowID().getValue());
                   }

                   if (!ok.booleanValue())
                   {
                      setErrorOnSave();
                   }
                   retval.assign(ok);
                   return;
                }

                // first, detect whether the record loaded in buffer has been changed. If so (changed or modified),
                // update the DATA-SOURCE-MODIFIED attribute for me, my temp-table, the temp-table of the
                // after-buffer, and dataset. The newly created record will not affect this attribute.
                // NOTE: the attribute/function name suggests that the data source buffer was altered but, from my
                //       tests, changing the source buffers does not affect this attribute. Having a modified or
                //       deleted before-image loaded in before-buffer(this) will set the attribute, instead.
                if (rowState == ROW_MODIFIED || rowState == ROW_DELETED)
                {
                   BufferImpl.this.setDataSourceModified(true);
                   tableHandleNative().setDataSourceModified(true);
                   after.tableHandleNative().setDataSourceModified(true);
                   peerBuffer.dataSet.setDataSourceModified(true);
                }

                logical ok = new logical();
                ErrorManager.silent(() -> ok.assign(
                      srcBuff._find(saveWhere, FindMode.UNIQUE, LockType.EXCLUSIVE, BufferImpl.this)));

                if (ok.isUnknown() || !ok.getValue() || !srcBuff._available())
                {
                   if (rowState == ROW_DELETED)
                   {
                      // this is a silent acceptance of the fact that the record was deleted from both
                      // locations (source and dataset table)

                      // in any case, update peer: the deleted row is now gone for good
                      setOriginRowidImpl(null);
                      retval.assign(true);
                      return;
                   }

                   ErrorManager.recordOrShowError(11912, false, srcBuff.doGetName());
                   // Unable to find record for db buffer <name> during SAVE-ROW-CHANGES
                   setErrorOnSave();
                   retval.assign(false);
                   return;
                }

                if (rowState == ROW_DELETED)
                {
                   srcBuff.deleteRecord();
                   // update peer: the deleted row is now gone for good
                   setOriginRowidImpl(null);
                   retval.assign(true);
                   return;
                }

                if (rowState == ROW_MODIFIED)
                {
                   after.findByRowID(afterRowid());

                   boolean preferDataset = dSrc.getPreferDataset().getValue();
                   boolean mergeByField = dSrc.getMergeByField().getValue();
                   ArrayList<TableMapper.LegacyFieldInfo> myFields =
                         TableMapper.getLegacyOrderedList(BufferImpl.this, true);
                   logical res = new logical();

                   try
                   {
                      if (preferDataset)
                      {
                         if (!mergeByField)
                         {
                            // overwrite and lose any DB changes to entire row
                            res.assign(TemporaryBuffer.copy(srcBuff.buffer(),
                                                            after.buffer(),
                                                            allSrcFields,
                                                            allPairs,
                                                            logical.of(noLobs)));
                         }
                         else
                         {
                            // save DS changes. Non overlapping DB field changes are kept.
                            StringBuilder sb = new StringBuilder();
                            for (int i = 0; i < myFields.size(); i++)
                            {
                               TableMapper.LegacyFieldInfo fieldInfo = myFields.get(i);
                               String fldName = fieldInfo.getLegacyName();
                               boolean modified;

                               if (fieldInfo.getExtent() == 0)  // scalar mode
                               {
                                  BaseDataType fldVal = Session.PK.equals(fldName) ? after.rowID()
                                                                                   : after.dereference(fldName);
                                  modified = !areBDTsEqual(fldVal, dereference(fldName));
                               }
                               else //extent mode
                               {
                                  modified = false;
                                  BaseDataType[] srcVals = (BaseDataType[]) srcBuff.derefPoly(fldName);
                                  BaseDataType[] originalVals = (BaseDataType[]) derefPoly(fldName);
                                  for (int j = 0; j < srcVals.length; j++)
                                  {
                                     if (!areBDTsEqual(srcVals[j], originalVals[j]))
                                     {
                                        modified = true;
                                        break;
                                     }
                                  }
                               }

                               if (modified)
                               {
                                  DataSource.FieldReference srcField = getMappedField(after, fldName);
                                  if (srcField == null || srcField.getBuffer() != srcBuff)
                                  {
                                     continue;
                                  }

                                  String srcName = srcField.getName();
                                  if (sb.length() != 0)
                                  {
                                     sb.append(",");
                                  }
                                  sb.append(fldName).append(",").append(srcName);
                               }
                            }
                            character dsChangedPairs = new character(sb.toString());

                            res.assign(TemporaryBuffer.copy(srcBuff.buffer(),
                                                            after.buffer(),
                                                            allSrcFields,
                                                            dsChangedPairs,
                                                            logical.of(noLobs)));
                         }
                      }
                      else
                      {
                         if (!mergeByField)
                         {
                            // if any field was modified in database raise error, otherwise update all fields
                            for (int j = 0; j < myFields.size(); j++)
                            {
                               TableMapper.LegacyFieldInfo fieldInfo = myFields.get(j);
                               String fldName = fieldInfo.getLegacyName();
                               DataSource.FieldReference srcField = getMappedField(after, fldName);
                               if (srcField == null || srcField.getBuffer() != srcBuff)
                               {
                                  continue;
                               }

                               String srcName = srcField.getName();
                               boolean changed;

                               if (fieldInfo.getExtent() == 0) // scalar mode
                               {
                                  BaseDataType srcVal = Session.PK.equals(srcName) ? srcBuff.rowID()
                                        : srcBuff.dereference(srcName);

                                  changed = !areBDTsEqual(srcVal, (dereference(fldName)));
                               }
                               else // extent mode
                               {
                                  changed = false;
                                  BaseDataType[] srcVals = (BaseDataType[]) srcBuff.derefPoly(srcName);
                                  BaseDataType[] originalVals = (BaseDataType[]) derefPoly(fldName);
                                  for (int i = 0; i < srcVals.length; i++)
                                  {
                                     if (!areBDTsEqual(srcVals[j], originalVals[j]))
                                     {
                                        changed = true;
                                        break;
                                     }
                                  }
                               }

                               // before-buffer and source must be the same (otherwise, there was a change
                               // in the database).
                               if (changed)
                               {
                                  // synchronize before & after buffers with the source
                                  TemporaryBuffer.copy(after.buffer(),
                                                       srcBuff.buffer(),
                                                       allSrcFields,
                                                       allPairs,
                                                       logical.of(noLobs));
                                  setUpBeforeBuffer(true);
                                  TemporaryBuffer.copy(buffer(),
                                                       srcBuff.buffer(),
                                                       allSrcFields,
                                                       allPairs,
                                                       logical.of(noLobs));
                                  setUpBeforeBuffer(false);

                                  // set ERROR & DATA-SOURCE-MODIFIED attributes
                                  ErrorManager.recordOrShowError(11913, false, srcBuff.doGetName());
                                  // SAVE-ROW-CHANGES found <name> record with conflicting change by another user.
                                  setErrorOnSave();
                                  res.assign(false);
                                  break;
                               }
                            }

                            if (res.isUnknown()) // ie. no 11913 was encountered
                            {
                               res.assign(TemporaryBuffer.copy(srcBuff.buffer(),
                                                               after.buffer(),
                                                               allSrcFields,
                                                               allPairs,
                                                               logical.of(noLobs)));
                            }
                         }
                         else
                         {
                            // attempt to save DS changes for each field. If the field was altered in source
                            // buffer raise error and return.
                            StringBuilder sb = new StringBuilder();
                            nextField: for (int i = 0; i < myFields.size(); i++)
                            {
                               TableMapper.LegacyFieldInfo fieldInfo = myFields.get(i);
                               String fldName = fieldInfo.getLegacyName();
                               boolean isExtent = fieldInfo.getExtent() != 0;
                               boolean sourceModified = false;
                               String srcName = null;

                               if (isExtent)
                               {
                                  BaseDataType[] fldVals = (BaseDataType[]) after.derefPoly(fldName);
                                  BaseDataType[] originalVals = (BaseDataType[]) derefPoly(fldName);

                                  boolean modified = false;
                                  for (int j = 0; j < fldVals.length; j++)
                                  {
                                     if (!areBDTsEqual(fldVals[j], originalVals[j]))
                                     {
                                        modified = true;

                                        DataSource.FieldReference srcField = getMappedField(after, fldName);
                                        if (srcField == null || srcField.getBuffer() != srcBuff)
                                        {
                                           continue nextField;
                                        }

                                        srcName = srcField.getName();
                                        sourceModified = !areBDTsEqual(srcBuff.dereference(j + 1, srcName), originalVals[j]);
                                        break;
                                     }
                                  }

                                  if (!modified)
                                  {
                                     continue;
                                  }
                               }
                               else
                               {
                                  BaseDataType fldVal;
                                  BaseDataType originalVal;

                                  if (Session.PK.equals(fldName))
                                  {
                                     fldVal = after.rowID();
                                     originalVal = rowID();
                                  }
                                  else
                                  {
                                     fldVal = after.dereference(fldName);
                                     originalVal = dereference(fldName);
                                  }

                                  if (!areBDTsEqual(fldVal, originalVal))
                                  {
                                     DataSource.FieldReference srcField = getMappedField(after, fldName);
                                     if (srcField == null || srcField.getBuffer() != srcBuff)
                                     {
                                        continue;
                                     }

                                     srcName = srcField.getName();
                                     BaseDataType srcVal = Session.PK.equals(srcName) ? srcBuff.rowID()
                                           : srcBuff.dereference(srcName);
                                     sourceModified = !areBDTsEqual(srcVal, originalVal);
                                  }
                                  else
                                  {
                                     continue;
                                  }
                               }

                               if (sourceModified)
                               {
                                  // synchronize before & after buffers with the source
                                  TemporaryBuffer.copy(after.buffer(),
                                                       srcBuff.buffer(),
                                                       allSrcFields,
                                                       allPairs,
                                                       logical.of(noLobs));
                                  setUpBeforeBuffer(true);
                                  TemporaryBuffer.copy(buffer(),
                                                       srcBuff.buffer(),
                                                       allSrcFields,
                                                       allPairs,
                                                       logical.of(noLobs));
                                  setUpBeforeBuffer(false);

                                  // set ERROR & DATA-SOURCE-MODIFIED attributes
                                  ErrorManager.recordOrShowError(11913, false, srcBuff.doGetName());
                                  // SAVE-ROW-CHANGES found <name> record with conflicting change by another user.
                                  res.assign(false);
                                  break;
                               }

                               if (sb.length() != 0)
                               {
                                  sb.append(",");
                               }
                               sb.append(fldName).append(",").append(srcName);
                            }

                            if (res.isUnknown()) // ie. no 11913 was encountered
                            {
                               res.assign(TemporaryBuffer.copy(srcBuff.buffer(),
                                                               after.buffer(),
                                                               allSrcFields,
                                                               new character(sb.toString()),
                                                               logical.of(noLobs)));
                            }
                         }
                      }
                   }
                   catch (ErrorConditionException ex)
                   {
                      ErrorManager.recordOrShowError(11910, srcBuff.doGetName());
                      // SAVE-ROW-CHANGES was unable to update the database table <table>.
                      setErrorOnSave();
                      retval.assign(false);
                      return;
                   }

                   srcBuff.release();
                   if (!res.booleanValue())
                   {
                      setErrorOnSave();
                   }
                   retval.assign(res);
                   return;
                }
             }
          });

          if (retval.isUnknown())
          {
             // something has gone wrong, there is no other valid value for [rowState]
             setErrorOnSave();
             retval.assign(false);
          }

          return retval;
       }

       /**
        * A method for comparing BDTs, used to detect modified fields in saveRowChangesImpl2.
        * character fields are compared using their String representation, this matches
        * SAVE-ROW-CHANGES behavior in 4GL.
        *
        * @param   op1
        *          The first operand of the method.
        * @param   op2
        *          The second operand of the method.
        *
        * @return  true if the operands' values are equal.
        */
       private boolean areBDTsEqual(BaseDataType op1, BaseDataType op2)
       {
          if (op1 instanceof Text && op2 instanceof Text)
          {
             return  Text.valueEquals((Text) op1, (Text) op2);
          }
          else
          {
             return op1.equals(op2);
          }
       }

       /**
        * Returns the mapped {@code FieldReference} of a specific field of a buffer. To obtain the result to be
        * returned, the buffer's {@code dataSrcMapping} is iterated for a matching key. If the buffer hasn't a
        * datasource attached (hence the {@code dataSrcMapping}) is {@code null} or the field has no mapping,
        * {@code null} is returned.
        *
        * @param   buffer
        *          The buffer to be investigated.
        * @param   fldName
        *          The field name.
        *
        * @return  The {@code FieldReference} mapped to the field in the attached data source or {@code null}
        *          otherwise.
        */
       static DataSource.FieldReference getMappedField(BufferImpl buffer, String fldName)
       {
          if (buffer.dataSrcMapping == null)
          {
             return null;
          }

          for (Pair<String, DataSource.FieldReference> entry : buffer.dataSrcMapping)
          {
             if (entry.getKey().equalsIgnoreCase(fldName))
             {
                return entry.getValue();
             }
          }
          return null;
       }

       /**
        * Sets the {@code ERROR} attribute to {@code true} after a failed SAVE-ROW operation.
        * Affected entities are:
        * <ul>
        *    <li>current IMAGE (it is a BEFORE IMAGE)</li>
        *    <li>the AFTER-IMAGE of this BEFORE-IMAGE</li>
        *    <li>the TEMP-TABLE of current BUFFER</li>
        *    <li>the TEMP-TABLE of the AFTER-BUFFER</li>
        *    <li>the DATASET of the BUFFER</li>
        * </ul>
        *
        * The  is not affected.
        * <p>
        * The attribute is not set to FALSE on a successful SAVE operation, the programmer must do
        * this by hand.
        */
       private void setErrorOnSave()
       {
          if (!isBeforeBuffer())
          {
             return;
          }

          error(true);               // current IMAGE (it is a BEFORE IMAGE)
          peerBuffer.error(true);    // the AFTER-IMAGE of this BEFORE-IMAGE

          TempTable beforeTable = TempTableBuilder.asTempTable((Temporary) this);
          TempTable afterTable = beforeTable.getAfterTableNative();

          beforeTable.error(true);   // the TEMP-TABLE of current BUFFER
          afterTable.error(true);    // the TEMP-TABLE of the AFTER-BUFFER

          DataSet ds = this.dataSet != null ? this.dataSet : peerBuffer.dataSet;
          if (ds != null && ds.valid())
          {
             ds.error(true);       // the DATASET of the BUFFER
          }
       }

       /**
        * Read record data from the ObjectInputStream
        *
        * @param   fields
        *          Record fields
        * @param   extents
        *          Record extents
        * @param   ois
        *          Source stream
        *
        * @return  Values of the record fields or <code>null</code> if failed.
        */
       private Map<String, BaseDataType[]> readValues(ArrayList<TableMapper.LegacyFieldInfo> fields,
                                                      Map<String, Integer> extents,
                                                      ObjectInputStream ois)
       {
          Map<String, BaseDataType[]> values = new HashMap<>();
          try
          {
             for (int i = 0; i < fields.size(); i++)
             {
                TableMapper.LegacyFieldInfo lfi = fields.get(i);
                String prop = lfi.getJavaName();
                Integer extent = extents.get(prop);

                Class<? extends BaseDataType> cls = lfi.getDataType();
                BaseDataType[] val;

                if (extent == null)
                {
                   // scalar case
                   val = new BaseDataType[1];
                   // call the default constructor
                   val[0] = BaseDataTypeFactory.instantiate(cls);

                   // deserialize
                   val[0].readExternal(ois);
                }
                else
                {
                   // array case
                   val = new BaseDataType[extent];
                   for (int j = 0; j < extent; j++)
                   {
                      // call the default constructor
                      val[j] = BaseDataTypeFactory.instantiate(cls);

                      // deserialize
                      val[j].readExternal(ois);
                   }
                }
                values.put(prop, val);
             }

             return values;
          }
          catch (IOException | SecurityException | ReflectiveOperationException | IllegalArgumentException ex)
          {
             // TODO: if something happened during the copy but after the create, do we need to put
             //       the original record back in?
             ErrorManager.recordOrShowError(
                   4952,
                   "Couldn't extract raw value from source field of RAW-TRANSFER",
                   false, false, true
             );
             return null;
          }
       }

       /**
        * FILL operation worker.
        */
       private class FillWorker
       {
          /** Buffer reference */
          private final BufferImpl selfBuf = BufferImpl.this;

          /** Buffer handle */
          private final handle self = new handle(selfBuf);

          /** FILL result */
          private final logical fillOk = new logical(true);

          /** FILL in append mode flag */
          private final boolean isAppend = FM_APPEND.equals(fillMode);

          /** FILL in merge mode flag */
          private final boolean isMerge = FM_MERGE.equals(fillMode);

          /** FILL in replace mode flag */
          private final boolean isReplace= FM_REPLACE.equals(fillMode);

          /** Number of source buffers */
          private int srcBufCount;

          /** Source buffers */
          private BufferImpl[] srcBuffs;

          /** Target record buffer */
          private RecordBuffer recBuf;

          /**
           * Fill the target DATA-SET.
           *
           * @return 'true' on success
           */
          public logical fill()
          {
             CallbackData bed = (callbacks == null) ? null : callbacks.get(BEFORE_FILL_EVENT);
             boolean ok;
             if (bed != null)
             {
                ok = dataSet.invokeCallback(self, bed);
                if (!ok)
                {
                   dataSet.error(true);
                   return logical.FALSE;
                }
             }
             else if (dataSource == null || !dataSource.valid())
             {
                ErrorManager.recordOrShowError(11874, false, doGetName());
                // FILL requires an attached data-source for buffer %1, or an active before-fill callback procedure.
                dataSet.error(true);
                return logical.FALSE;
             }

             if (TempTableBuilder.asTempTable((Temporary) selfBuf).isTrackingChanges().getValue())
             {
                ErrorManager.recordOrShowError(11863, doGetName());
                // FILL is not valid for table <name> when TRACKING-CHANGES is on.
                dataSet.error(true);
                return logical.FALSE;
             }

             if (dataSource != null && dataSource.valid())
             {
                // check DATA-SOURCE sanity and decide whether we need to create a new query
                srcBufCount = dataSource._getNumSourceBuffers();
                if (srcBufCount > 1 && !dataSource.getQueryAsHandle()._isValid())
                {
                   ErrorManager.recordOrShowError(11980);
                   // Must assign a user query to a data-source that is a join before filling.
                   ErrorManager.recordOrShowError(11875, doGetName());
                   // FILL of buffer <name> requires a DATA-SOURCE QUERY to be prepared and assigned for this type of buffer.
                   dataSet.error(true);
                   return logical.FALSE;
                }

                // we can start FILLing the table now
                if (FM_EMPTY.equalsIgnoreCase(fillMode))
                {
                   deleteAll();
                }

                initBuffers();
                if (isReplace)
                {
                   P2JIndex primaryIndex = recBuf.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

                      dataSet.error(true);
                      return logical.FALSE;
                   }
                }

                boolean queryOpen = true;
                boolean queryClose = true;
                QueryWrapper fillQuery = null;
                if (reusableQuery != null && dataSet.isReuseFillQuery())
                {
                   queryClose = false;
                   queryOpen = false;
                   fillQuery = reusableQuery;
                }

                if (fillQuery == null)
                {
                   handle hQuery = dataSource.getQueryAsHandle();
                   if (!hQuery._isValid())
                   {
                      dataSet.error(true);
                      return new logical(false);
                   }
                   fillQuery = (QueryWrapper) hQuery.getResource();
                   if (fillQuery.prepareString().isUnknown())
                   {
                      ErrorManager.recordOrShowError(11876, doGetName());
                      // FILL of buffer <name> with user-supplied query requires that the query be QUERY-PREPARED.
                      dataSet.error(true);
                      return new logical(false);
                   }

                   if (dataSet.isReuseFillQuery())
                   {
                      queryClose = false;
                      queryOpen = true;
                      reusableQuery = fillQuery;
                   }
                }

                Boolean secondPassRequired = null;
                Optional<DataRelation> relation = dataSet.getRelation(selfBuf);
                List<DataRelation> relations = dataSet.getRelations(selfBuf, true, false, true);
                boolean recursive = relation.map(DataRelation::isRecursive)
                                            .map(logical::booleanValue).orElse(false);
                if (recursive)
                {
                   secondPassRequired = secondPassRequired(relation.get(), fillQuery);
                   if (secondPassRequired == null)
                   {
                      ErrorManager.recordOrShowError(14282);
                      // Unable to recurse during FILL. (14282)
                      relations = Collections.emptyList();
                   }
                }

                processDataSource(fillQuery, relations, queryOpen, queryClose);

                if (Boolean.TRUE.equals(secondPassRequired))
                {
                   if (reusableQuery != null)
                   {
                      clearQuery();
                   }

                   fillQuery.setDataRelation(relation.get());
                   processDataSource(fillQuery, Collections.emptyList(), true, true);
                }
             }

             ok = dataSet.invokeCallback(self, (callbacks == null) ? null : callbacks.get(AFTER_FILL_EVENT));
             ok = ok && fillOk.booleanValue();
             dataSet.error(!ok);
             return logical.of(ok);
          }

          /**
           * Check if second pass is required (for recursive relation)
           *
           * @param   relation
           *          The relation to be processed.
           * @param   fillQuery
           *          The source query for the FILL operation.
           *
           * @return  <code>Boolean.TRUE</code> if second pass is required.
           */
          private Boolean secondPassRequired(DataRelation relation, P2JQuery fillQuery)
          {
             if (!(fillQuery instanceof QueryWrapper))
             {
                return null;
             }

             QueryWrapper wrapper = (QueryWrapper) fillQuery;
             RecordBuffer[] buffers = wrapper.getRecordBuffers();
             String[] relFields = relation.getRelationFields(false);
             DmoMeta dmoMeta = buffers[0].getDmoInfo();

             for (String relField: relFields)
             {
                if (dmoMeta.byLegacyName(relField) == null)
                {
                   BufferReference bufferRef = (BufferReference) relation.getChildBuffer().getResource();
                   String legacyTable = bufferRef.buffer().getDmoInfo().legacyTable;
                   ErrorManager.recordOrShowError(11883, dmoMeta.legacyTable, relField, legacyTable);
                   return null;
                }
             }

             for (String relField: relation.getRelationFields(true))
             {
                if (dmoMeta.byLegacyName(relField) == null)
                {
                   return Boolean.FALSE;
                }
             }

             return Boolean.TRUE;
          }

          /**
           * Process the DATA-SOURCE.
           *
           * @param   fillQuery
           *          The source query for the FILL operation.
           * @param   childRelations
           *          The list of child relations.
           * @param   open
           *          Flag indicating that the fill query needs to be opened.
           * @param   close
           *          Flag indicating that the fill query needs to be closed.
           */
          private void processDataSource(P2JQuery           fillQuery,
                                         List<DataRelation> childRelations,
                                         boolean            open,
                                         boolean            close)
          {
             ErrorHelper errHlp = recBuf.bufferManager.getTxHelper().errHlp;
             boolean wmOn = errHlp.isWarningMode();
             if (!wmOn)
             {
                errHlp.setWarningMode(true);
             }

             try
             {
                RecordBuffer buffer = buffer();

                if (open)
                {
                   fillQuery.hintFullResults();
                   fillQuery.queryOpen();
                }
                else
                {
                   fillQuery.clearResults();
                }

                fillQuery.first();
                Long[] rowids = dataSource.getRestartRowids();
                if (rowids[0] != null)
                {
                   fillQuery.repositionByID(rowids);
                   fillQuery._getNext();
                }
                else
                {
                   int value = dataSource.getRestartRow().intValue();
                   if (value > 0)
                   {
                      fillQuery.reposition(value);
                      fillQuery._getNext();
                   }
                }

                // iterate the result and populate this buffer
                integer batchRemaining = new integer(batchSize > 0 ? batchSize : -1);
                logical testRes = new logical(); // safe to use, as is just assigned on each evaluation
                LogicalOp test = () ->
                {
                   testRes.assign(!fillQuery._isOffEnd() && batchRemaining.intValue() != 0);
                   return testRes;
                };
                String lbl = "fill-buffer-" + name;
                logical hasMore = new logical();
                Map<String, FieldStructure> fields = new HashMap<>();
                Runnable batchFill = () ->
                {
                   recBuf.bufferManager.addDirtyBatchBuffer(buffer, null);
                   if (!fillRow(fillQuery, hasMore, buffer, fields))
                   {
                      BlockManager.next(lbl);
                   }
                };
                Runnable update = () -> RecordBuffer.batch(batchFill);

                BlockManager.repeatToWhile(TransactionType.SUB, lbl, null, test,
                         new Block((Body) () ->
                         {
                            if (!fillOk.getValue())
                            {
                               BlockManager.leave();
                            }

                            if (callbacks != null &&
                                !dataSet.invokeCallback(self, callbacks.get(BEFORE_ROW_FILL_EVENT)))
                            {
                               fillOk.assign(false);
                               BlockManager.leave();
                            }

                            selfBuf.create();
                            if (!isMerge && !isReplace)
                            {
                               try
                               {
                                  update.run();
                               }
                               catch (ErrorConditionException var10)
                               {
                                  fillOk.assign(false);
                                  ErrorManager.recordOrShowError(11878, false, "-1", doGetName());
                                  // FILL got error -1 creating record <buffer-name> (11878)
                                  BlockManager.leave();
                               }
                            }
                            else
                            {
                               recBuf.txHelper.errHlp.silent(update);
                               recBuf.txHelper.errHlp.setPending(false);
                            }

                            if (batchRemaining.intValue() > 0)
                            {
                               batchRemaining.decrement();
                            }

                            if (callbacks != null &&
                                !dataSet.invokeCallback(self, callbacks.get(AFTER_ROW_FILL_EVENT)))
                            {
                               selfBuf.release();
                               fillOk.assign(false);
                               BlockManager.leave();
                            }

                            int childRelationsSize = childRelations.size();
                            for (int i = 0; i < childRelationsSize; i++)
                            {
                               DataRelation rel = childRelations.get(i);
                               Buffer childBuffer = rel.getChildBufferNative();
                               if (rel._isReposition())
                               {
                                  childBuffer.lastBatch(true);
                               }
                               else if (!rel._isRecursive() || childBuffer != selfBuf)
                               {
                                  fillOk.assign(childBuffer.fill().getValue());
                                  if (!fillOk.getValue())
                                  {
                                     break;
                                  }
                               }
                            }

                            selfBuf.release();
                            if (!fillOk.getValue())
                            {
                               BlockManager.leave();
                            }

                            hasMore.assign(fillQuery._getNext());
                         }));
                if (batchSize > 0 && batchRemaining.intValue() != 0 && childRelations.isEmpty())
                {
                   // the batch ended before [batchSize] records were processed
                   lastBatch = true;
                }
                else if (!hasMore.booleanValue())
                {
                   // the last invocation to fill-query:get-next() returned false so there are no more batches
                   lastBatch = true;
                }
             }
             finally
             {
                dataSource.updateFillRowidInfo(fillOk.getValue());
                if (close)
                {
                   fillQuery.setDataRelation(null);
                   fillQuery.queryClose();
                }

                selfBuf.release();
                if (!wmOn)
                {
                   errHlp.setWarningMode(false);
                }
             }
          }

          /**
           * Initialize buffers
           */
          private void initBuffers()
          {
             srcBuffs = new BufferImpl[srcBufCount];

             for (int k = 0; k < srcBufCount; ++k)
             {
                srcBuffs[k] = (BufferImpl) dataSource.getSourceBuffer(k + 1).unwrapBuffer();
                srcBuffs[k].buffer().initialize();
             }

             recBuf = buffer();
             recBuf.initialize();
             flushAll();
          }

          /**
           * Populate the target record by iterating the data-source mapping and copying values between paired
           * fields.
           *
           * @param   fillQuery
           *          The source query.
           * @param   hasMore
           *          Flag indicating that more records are available.
           * @param   dstBuffer
           *          The destination record buffer.
           * @param   fields
           *          A map of resolved {@link FieldStructure} for each destination field.
           *
           * @return  {@code true} on success.
           */
          private boolean fillRow(P2JQuery                    fillQuery,
                                  logical                     hasMore,
                                  RecordBuffer                dstBuffer,
                                  Map<String, FieldStructure> fields)
          {
             int dataSrcMappingSize = dataSrcMapping.size();
             for (int i = 0; i < dataSrcMappingSize; i++)
             {
                Pair<String, DataSource.FieldReference> entry = dataSrcMapping.get(i);
                String myField = entry.getKey();
                DataSource.FieldReference srcField = entry.getValue();

                BufferImpl srcBuffer = srcField.getBuffer();
                if (srcField.getName().equals(Session.PK))
                {
                   dereference(myField, srcBuffer.buffer().rowID());
                }
                else
                {
                   assign(myField, srcField, srcBuffer, dstBuffer, fields);
                }
             }

             if (srcBufCount == 1)
             {
                TempRecord newRec = (TempRecord) recBuf.getCurrentRecord();
                newRec._datasourceRowid(srcBuffs[0].buffer()._rowID());
             }

             if (isMerge || isReplace || isAppend)
             {
                boolean validated = false;
                try
                {
                   validated = recBuf.validate(isAppend);
                }
                catch (ValidationException | ErrorConditionException e)
                {
                   if (isAppend)
                   {
                      // append mode: abort the fill operation
                      recBuf.release(true, false);
                      throw new ErrorConditionException(
                               "FILL: Failed with unique validation error in APPEND mode.", e);
                   }
                }

                // was there a unique conflict with this record?
                if (!validated)
                {
                   if (isMerge)
                   {
                      // merge mode: ignore it, just skip to next record
                      TransactionManager.disableLoopProtection(null);
                      hasMore.assign(fillQuery._getNext());
                      // we need to release the newly created record as if undone. We cannot call
                      // BlockManager.undoNext(lbl) here as it will affect the buffer(s) from the query
                      // driving the fill operation.
                      recBuf.release(true, false); // I.e. drop() - this form avoids a buffer() lookup
                      return false;
                   }
                   else if (isReplace)
                   {
                      // replace mode: eliminate competition, then try again on clean ground
                      try
                      {
                         OrmUtils.dropUniqueIndexConflicts(recBuf,
                                                           recBuf.getPersistence(),
                                                           recBuf.getMultiplexID());
                      }
                      catch (PersistenceException e)
                      {
                         throw new ErrorConditionException(
                                  "FILL: Failed to make room for new record in REPLACE mode", e);
                      }
                      // now there should not be any collisions and the new record should be flushed
                   }
                }
             }

             return true;
          }

          /**
           * Flush buffers
           */
          private void flushAll()
          {
             boolean inTx = false;

             try
             {
                inTx = recBuf.persistenceContext.beginTransaction(null);
                recBuf.flush();

                for (BufferImpl srcBuff : srcBuffs)
                {
                   srcBuff.buffer().flush();
                }
             }
             catch (PersistenceException | ValidationException exc)
             {
                ErrorManager.recordOrThrowError(exc);
             }
             finally
             {
                if (inTx)
                {
                   try
                   {
                      recBuf.persistenceContext.commit();
                   }
                   catch (PersistenceException exc)
                   {
                      ErrorManager.recordOrThrowError(exc);
                   }
                }
             }
          }
       }

       /**
        * A structure to cache the resolved information for the destination field, used during FILL operations.
        */
       private static class FieldStructure
       {
          /** The resolved extent index. */
          public int dstIndex;

          /** The resolved field name. */
          public String crtField;

          /** The associated property for this field. */
          public Property dstProp;

       }
    }