AbstractQuery.java

/*
** Module   : AbstractQuery.java
** Abstract : Abstract base class for all query implementations
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ECF 20060102   @23793 Created initial version. Retrofitted existing
**                           query classes to extend this class. Provides
**                           basic accumulation architecture.
** 002 ECF 20060118   @23988 Modified to accommodate Accumulator changes.
**                           Accumulator break group categories are now
**                           managed internally by the Accumulator class.
** 003 ECF 20060221   @24714 Added validateSubstitutionArguments. Ensures
**                           all arguments are either BaseDataTypes or
**                           Resolvables.
** 004 ECF 20060404   @25365 Implement WrappedResource. Required for
**                           method and attribute support.
** 005 ECF 20060419   @25613 Added support for deferred mode accumulators.
**                           In this mode, the query signals to the
**                           accumulator that a loop iteration has
**                           occurred, and that the query has found a new
**                           value to process, but accumulation is
**                           deferred until there is an explicit trigger
**                           to process accumulation, typically invoked by
**                           business logic.
** 006 ECF 20060503   @25960 Added support for accumulation using external
**                           data sources. Enhanced integration of break
**                           group information.
** 007 ECF 20060504   @25972 Removed redundant interface. P2JQuery now
**                           extends WrappedResource, so it is no longer
**                           necessary to declare that we implement it
**                           here.
** 008 ECF 20060912   @29597 Minor debugging improvement. Added index of
**                           invalid argument to IllegalArgumentException
**                           message in validateSubstitutionArguments().
** 009 ECF 20060925   @29960 Added safety check to argument validation.
**                           If an argument is null and an error is
**                           pending with the ErrorManager, an exception
**                           is not raised. Added checkPendingError()
**                           method for subclasses.
** 010 ECF 20061003   @30150 Rewrote checkPendingError() to release
**                           buffers in the event of a pending error.
**                           Renamed method to prepareFetch().
** 011 ECF 20061023   @30605 Abstracted errorIfNull logic up from
**                           subclasses. Implement setErrorIfNull() from
**                           P2JQuery interface. Added isErrorIfNull()
**                           method for subclass use.
** 012 ECF 20061102   @30956 Implement new P2JQuery methods which support
**                           browse capability. Specifically, a query
**                           associated with a browse widget must fetch
**                           the next record automatically when it is
**                           repositioned. Also, a browse must be notified
**                           of "asynchronous" reposition events.
** 013 ECF 20061119   @31322 Additional reposition support. Added methods
**                           isFetchOnReposition() and
**                           hasRepositionListener().
** 014 ECF 20061204   @31572 Added browsed flag and is/setBrowsed methods.
**                           Indicates if query is associated with a UI
**                           browse widget. This is necessary because the
**                           query must behave differently when attached
**                           to a browse.
** 015 ECF 20061206   @31584 Added parameter to afterReposition(). Allows
**                           caller to override fetchOnReposition behavior
**                           to expressly disallow record fetch.
** 016 SIY 20070502   @33428 Added notifyRepositionListeners() method.
** 017 ECF 20070716   @34506 Implement new methods required by P2JQuery
**                           interface:
**                              isStandalone()
**                              setStandalone(boolean)
**                              cleanup()
** 018 ECF 20070820   @34904 Added support for temporary silent error
**                           mode. This allows query substitution parms to
**                           be processed safely during query construction
**                           and initialization.
** 019 SVL 20080418   @38037 Added the inverseSorting flag.
** 020 ECF 20080509   @38240 Added open() method. Necessary for converted
**                           OPEN QUERY statements.
** 021 ECF 20080509   @38250 Fixed regression in open(). Removed check for
**                           standalone flag.
** 022 SVL 20080514   @38276 reposition(1) if the query is browsed was
**                           removed from open().
** 023 ECF 20080515   @38287 Rolled back #022 (@38276). The reposition is
**                           necessary in cases where the query is
**                           assigned to a QueryWrapper after that wrapper
**                           was already registered to a browse widget. In
**                           that case, we cannot rely on the browse
**                           driving the reposition.
** 024 CA  20080624   @38891 Minor javadoc changes regarding standalone 
**                           field and accessors.
** 025 ECF 20080630   @39032 Minor memory optimization. Use Boolean
**                           constants instead of instantiating new
**                           Boolean objects.
** 026 ECF 20081106   @40319 Added default implementation of isOffEnd().
**                           Uses _isOffEnd() as its backing worker.
** 027 CA  20081210   @40835 Implemented CLOSE QUERY statement.
** 028 CA  20090227   @41390 Added WrappedResources.isUnknown method; this
**                           implementation returns false.
** 029 ECF 20090307   @42982 Fixed converted OPEN QUERY behavior. Added
**                           loadRowAtCursor() and getOffEnd(), changed
**                           afterReposition() to only fetch a record if the
**                           query is browsed.
** 030 ECF 20090707   @43103 Added isRepositionNotificationActive(). Reports
**                           whether we are currently notifying listeners of a
**                           reposition event.
** 031 SVL 20091030   @44291 getOffEnd() function was made public in order to
**                           support API change into P2JQuery.
** 032 SIY 20100303   @44701 Added hasAccumulators() helper method.
** 033 VMN 20130207          Added support for handle-based methods:
**                           GET-BUFFER-HANDLE (KW_GET_BUFH)
**                           GET-CURRENT (KW_GET_CUR),
**                           QUERY-CLOSE (KW_QRY_CLOS),
**                           REPOSITION-FORWARD (KW_REPOS_F),
**                           REPOSITION-TO-ROW (KW_REPOS_2R)
**                           REPOSITION-TO-ROWID (KW_REPOS_2I),
**                           and attributes
**                           NUM-RESULTS (KW_NUM_RES)
** 034 OM  20130212          Added implementation stubs for some dynamic methods.
** 035 VMN 20130213          Added support for attributes
**                           INDEX-INFORMATION (KW_IDX_INFO)
**                           FORWARD-ONLY (KW_FWD_ONLY)
**                           PREPARE_STRING (KW_PREP_STR)
** 036 VMN 20130219          Added support for attributes
**                           IS-OPEN (KW_IS_OPEN)
** 037 CA  20130221          Added support for UNIQUE-ID, ADM-DATA. Implemented Deletable. Added
**                           HandleChain support. 
** 038 OM  20130304          Refactored isValid and isUnknown of WrappedResource to valid 
**                           and unknown.
** 039 VMN 20130306          Added addBuffer(handle bufHandle) and corrected support
**                           for handle-based methods:
**                           GET-NEXT (KW_GET_NEXT)
**                           GET-PREV (KW_GET_PREV)
**                           GET-FIRST (KW_GET_1ST)
**                           GET-LAST (KW_GET_LAST)
**                           GET-CURRENT (KW_GET_CUR)
**                           Implemented
**                           getFirst(),getNext(),getLast(),getPrevious(),getCurrent()
** 040 CA  20130311          Added bufferHandle(integer).
** 041 VMN 20130311          Implemented queryClose, queryForward,
**                           queryReposition, queryRepositionByID,
**                           removed static methods
** 042 VMN 20130312          Corrected conversion and runtime for
**                           REPOSITION-TO-ROW (KW_REPOS_2R)
** 043 CA  20130315          Added missing INDEX-INFORMATION API.
** 044 OM  20130320          Added implementation for ResultListHandler interface.
** 045 VMN 20130321          Corrected conversion and runtime for
**                           NUM-BUFFERS attribute(KW_NUM_BUFF)
** 046 CA  20130322          Added missing queryForward(integer) API.
** 047 SVL 20130325          Added include and exclude functions.
** 048 CA  20130603          resourceDelete must return a boolean value.
** 049 CA  20130918          Added widget-pool support. The pool name must be validated before 
**                           anything else, and a null name means the default pool is used.
** 050 CA  20130927          Removed unknown() API, as by default the resource is known. 
** 051 ECF 20130405          Moved createQuery static methods to QueryWrapper. Cleaned up some
**                           return values.
** 052 CA  20131024          The legacy QUERY resource is implemented by QueryWrapper in P2J. 
**                           A block-level query is not compatible with the legacy QUERY resource,
**                           thus the implementation for almost all QUERY-related APIs is moved to
**                           QueryWrapper (what was not moved yet are the GET-FIRST/LAST/NEXT/PREV
**                           /CURRENT implementations).
** 053 ECF 20131028          Import change.
** 054 VMN 20140330          Added LockType errors processing.
** 055 SVL 20140604          Implemented common logic for indexInformation() and
**                           indexInformation(int).
** 056 SVL 20140925          Added deleteResultListEntry.
** 057 ECF 20141124          Added queryBackward methods.
** 058 SVL 20150331          Added deleteResultListEntry(boolean).
** 059 SVL 20150722          Fix related to current row selection for browsed queries.
** 060 OM  20150727          Fixed implementation of get(Next/Last/Prev/First/Current) methods.
** 061 ECF 20150701          Added optimize method; enabled database statistics collection;
**                           replaced Apache commons logging with J2SE logging.
** 062 SVL 20150821          Added removeRepositionListener.
** 063 ECF 20150907          Added logic to preprocess query substitution parameters within
**                           proper error handling and to detect query nesting.
** 064 OM  20150915          Added stub for SKIP-DELETED-RECORD attribute and NUM-RESULTS
**                           function.
** 065 EVL 20160217          Clean up comments from symbols invalid for Solaris to compile.
** 066 SVL 20160314          Added parameterFilter.
** 067 ECF 20160725          Track external buffers referenced within a query. Implement Joinable
**                           instead of P2JQuery, provide unsupported operation implementations
**                           of some Joinable methods and some new P2JQuery methods.
** 068 IAS 20161012          Resolve XXXExpr instances (marked with QueryParam)
** 069 SVL 20170126          Added bufferHandle(), changed signature of
**                           queryForward/queryBackward.
** 070 ECF 20171128          When collecting referenced buffers, include inverse buffer of a
**                           natural join, if present.
** 071 GES 20171207          Switch to ErrorManager.silent().
**     ECF 20171212          Rework query substitution parameter error handling.
** 072 OM  20171129          releaseBuffers() is implemented from P2JQuery.
**     ECF 20171202          Added flag to optionally omit DMO aliases from the query's HQL.
** 073 ECF 20180201          Removed optimize method. Cleaned up inheritance hierarchy.
** 074 OM  20180220          Added outer-join attribute.
** 075 ECF 20180418          Added getExternalBuffers.
** 076 OM  20181031          Added BufferManager-level unknown mode support.
** 077 OM  20181206          Added support for dynamic evaluations and QueryOpen.
** 078 ECF 20190302          Renamed setExternalBuffers to addExternalBuffers and changed
**                           implementation, since it may be called multiple times on a query.
** 079 SVL 20190320          Added createResultListEntry.
** 080 OM  20190327          Renamed DataSource to avoid conflicts with DataSet source.
** 081 CA  20190811          Do not use WeakList for openListeners - as they may get garbage 
**                           collected BEFORE they are used; and they also must live for the 
**                           duration of the query.
**                           A dynamic query can be closed and then re-opened.
** 082 CA  20191009          Added where and sort clause translation in case of bound buffers.
** 083 SVL 20191218          Handled case when REPOSITION TO ROWID fails.
** 084 CA  20200428          Added 'missing' for exclude() and include() APIs (FIELDS/EXCEPT for 
**                           record-phrase).
**     CA  20200503          Added QUERY:CACHE runtime stub.
** 085 ECF 20200906          New ORM implementation.
** 086 GES 20200731          Eliminated context-local lookups by using ErrorHelper.
**     OM  20200915          Added reason of query navigation fatal error cases to log.
**     CA  20200918          INDEX-INFORMATION requires to interpret the 'execute' method for a dynamic query
**                           without evaluating the dynamic calls in the WHERE.
**     CA  20210216          Arithmetic operations are always int64.
**     OM  20210219          bufferHandle() accepts int64 expression parameters.
**     CA  20210310          A dynamic predicate must set the default lock to NONE, instead of SHARE.
**     ECF 20210723          Javadoc fix.
**     ECF 20210806          Fixed nested query (7254) error handling.
**     AL2 20210909          Added support for indexed reposition.
**     OM  20210917          Code maintenance.
**     OM  20220112          Added implementation of SKIP-DELETED-RECORD attribute.
**     AL2 20220412          Do proxy checks for values used as query parameters.
**     CA  20221109          Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded 
**                           as 'partial/incomplete'.
**     OM  20221103          New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache.
**     CA  20221130          Refactored the WHERE clause translation (when the bound and definition buffers
**                           are not the same), to be aware of the external buffers (i.e. added for CAN-FIND
**                           sub-select clauses).  The translate will be performed before the query is being
**                           executed.
**     CA  20221109          Implemented runtime for EXCEPT/FIELDS options - only NO-LOCK records are loaded 
**                           as 'partial/incomplete'.
** 087 CA  20230227          Fixed where clause translation when the external buffer's binding and definition
**                           does not match.   Also, buffer match check must include the DMO alias.
** 088 CA  20230323          Prevent NPE if the field in EXCEPT/FIELDS can't be resolved in that buffer.
** 089 IAS 20230405          Fixed FILL support for recursive DATA-RELATION
** 090 AL2 20230411          Support query close listeners.
** 091 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 092 OM  20230511          Changed signature of FQLPreprocessor.translate().
** 093 RAA 20231010          Removed DatabaseStatistics usage.
** 094 AL2 20230828          Removed QueryOffEndException.
** 095 CA  20240320          Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for',  
**                           where it applies.
** 096 CA  20240324          Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 097 ES  20240404          Added new setIteration, isIteration, setPresort and isPresort methods.
**                           A query has presort flag set if it a PresortQuery or it is a 
**                           component PresortCompoundQuery.
** 098 OM  20240424          Allow the expanded fields to be collected from FIELDS query option.
** 099 AL2 20240625          Query parameters that throw an exception on evaluation will now return ?.
**     AL2 20240626          preprocessSubstitutionArguments should return true even if the parameter 
**                           evaluation fails.
** 100 CA  20240809          Removed capturing lambda from 'setUnknownMode'.
** 101 DDF 20240828          Added silent method equivalents to first, next, previous, last, unique, current
**                           to avoid using ErrorManager.silent() which requires a lambda. Added errorHelper
**                           property.
** 102 SP  20241016          Added caching of translateSort() output.
** 103 DDF 20230713          Added setForwardOnly() and isForwardOnly() methods.
**     DDF 20230717          Added missing @Override annotation.
** 104 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
**                           based on [sharedDb] parameter.
**                           Implementation of [setMultiTenantAlternative] method.
** 105 SP  20250108          Added dmo interfaces to the SortCacheKey.
** 106 AL2 20250416          Added state support for force-database-join.
** 107 CA  20250422          Added support for REPOSITION-BY-ID(rowid[]), where the arg is an extent.
** 108 AL2 20250521          Fetch on reposition due to browse should be done with NO_LOCK.
*/

/*
** 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.util.*;
import java.util.function.*;
import java.util.logging.*;

import com.goldencode.cache.*;
import com.goldencode.p2j.persist.event.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.ErrorManager;
import com.goldencode.p2j.util.ErrorManager.ErrorHelper;
import com.goldencode.p2j.util.logging.*;

/**
 * Abstract base class for all query implementation classes.  Implements the {@code Joinable}
 * interface and provides base services common to all query implementations.
 */
public abstract class AbstractQuery
implements P2JQuery
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(AbstractQuery.class.getName());
   
   /**
    * Indicates whether the query has the inverse sorting direction against
    * the original sorting. E.g.,
    * <pre>
    *  find next tt use-index si-name.
    *  find prev tt use-index si-name.
    * </pre>
    * will be converted to the code having a couple of AdaptiveFind queries:
    * <pre>
    *  AdaptiveFind query1 = new AdaptiveFind(tt, null, "tt.name asc");
    *  AdaptiveFind query2 = new AdaptiveFind(tt, null, "tt.name desc", true);
    * </pre>
    * and the second query will have the inverse sorting direction.
    */
   protected boolean inverseSorting = false;
   
   /** Flag indicating if this query was explicitly closed. */
   protected boolean closed = false;
   
   /** The filter that handles parameters passed to the query. */
   protected ParameterFilter parameterFilter = null;
   
   /** Flag indicating this query is associated with a dynamically prepared query resource. */
   protected boolean dynamicPredicate = false;
   
   /** Flag to indicate if this query is optimized using indexed reposition */
   protected boolean indexedReposition = false;
   
   /** Flag to indicate if this query should enforce database joins and bypass internal algorithm. */
   protected boolean forceDatabaseJoin = false;
   
   /** The included properties for each DMO (FIELDS option). */
   protected Map<DataModelObject, Property[]> included = null;

   /** The excluded properties for each DMO (EXCEPT option). */
   protected Map<DataModelObject, Property[]> excluded = null;
   
   /** Flag indicating fatal error was encountered processing query substitution parameters */
   private boolean fatalError = false;
   
   /** Should the failed first/last/unique retrievals raise error? */
   private boolean errorIfNull = true;
   
   /** <code>false</code> if running off end of results should raise error */
   protected boolean lenientOffEnd = false;
   
   /** Marks this as a left outer join component of the query. */
   protected boolean outer = false;
   
   /** Context-local error helper used for silent mode operations. */
   protected ErrorHelper errorHelper = null;
   
   /** Does this query stand alone or is it contained by a query wrapper ? */
   private boolean standalone = true;
   
   /** Should the next row be fetched after a reposition? */
   private boolean fetchOnReposition = false;
   
   /** Is query associated with a UI browse widget? */
   private boolean browsed = false;
   
   /** Corresponds FORWARD-ONLY attribute. */
   private boolean forwardOnly = false;
   
   /** Internal value of SKIP-DELETED-RECORD. */
   private boolean skipDeletedRecord = true;
   
   /** Flag indicating whether the query is used inside a FOR EACH block. */
   private boolean iterating = false;
   
   /** Flag indicating whether the BREAK BY statement is present */
   private boolean presort = false;
   
   /**
    * This flags brackets the code which is invoked by browse widgets for populating its rows. When active,
    * the deleted rows are not returned.
    */
   private boolean fillingBrowseRows = false;
   
   /** Accumulators added to this query, mapped to their deferred status */
   private LinkedHashMap<Accumulator, Boolean> accumulators = null;
   
   /** Registry of listeners who are notified of query repositions */
   private WeakList<RepositionListener> repoRegistry = null;
   
   /** Registry of listeners who are notified of query is about to (re-)open. */
   private List<QueryEventListener> eventListeners = null;
   
   /** Flag to avoid infinite recursion in reposition notification */
   private boolean notificationActive = false;
   
   /** Set of external buffers, if any, referenced by this query */
   private RecordBuffer[] externalBuffers = null;
   
   /** Set of external buffer definitions, if any, referenced by this query */
   private RecordBuffer[] externalBuffersDefs = null;
   
   /** Flag indicating that a DMO alias should not be used in this query's FQL statement */
   private boolean omitAlias = false;

   /** Flag indicating if {@link #afterReposition(boolean)} calls are skipped. */
   private boolean ignoreAfterRepo = false;
   
   /** The recursive DATA-RELATION the query is to FILL for */
   private DataRelation relation = null;

   /** Cache of translateSort output, indexed by bound/definition aliases and sort clause */
   private static LRUCache<AbstractQuery.SortCacheKey, String> sortCache;
   
   /**
    * Default constructor.
    */
   protected AbstractQuery()
   {
      
   }

   /**
    * Initializes the cache using CacheManager. The default value of the cache
    * is used when there is no size available from the configuration.
    */
   public static void initializeCache()
   {
      sortCache = CacheManager.createLRUCache(AbstractQuery.class, null, 2048);
   }
   
   /**
    * Implementation of NUM-RESULTS P4GL function.
    * 
    * @param   queryName
    *          A {@link character} expression that evaluates to the name of a currently open, 
    *          scrolling query.
    * 
    * @return  the number of rows currently in the results list of a scrolling query. If {@code
    *          queryName} does not resolve to the name of a query, or if the query is not open or
    *          not scrolling, then the function returns the Unknown value ({@code ?}).
    */
   public static integer numResults(character queryName)
   {
      UnimplementedFeature.missing("NUM-RESULTS not implemented.");
      return new integer();
   }
   
   /**
    * Implementation of NUM-RESULTS P4GL function.
    * 
    * @param   query
    *          A {@link character} expression that evaluates to the name of a currently open, 
    *          scrolling query.
    * 
    * @return  the number of rows currently in the results list of a scrolling query. If {@code
    *          queryName} does not resolve to the name of a query, or if the query is not open or
    *          not scrolling, then the function returns the Unknown value ({@code ?}).
    */
   public static integer numResults(P2JQuery query)
   {
      return query.size(); // getNumResults();
      // TODO: should return query.getNumResults(), query.size() returns current size of the
      //       cached results list, not the number of rows currently in the results list of a
      //       scrolling query.
   }
   
   /**
    * Iterate the array of query substitution parameters, if any, and resolve any which are
    * instances of <code>P2JQuery.Parameter</code> in silent error mode.
    * Those one which are instances of Resolvable are resolved in normal mode
    * <p>
    * An important secondary purpose of this method is to check whether we are processing a query
    * within the context of another query and raising an error condition if this is the case.
    * <p>
    * If an error is encountered resolving a parameter, the method will abort and set all values
    * in the {@code args} array to {@code null}. In this case, the method will return {@code
    * false} instead of {@code true}, and it is up to the caller to handle the {@code null}
    * arguments appropriately.
    * <p>
    * This error handling behavior differs from the 4GL, in which an error may preempt processing
    * some elements of the where clause, but continue evaluating others. This difference can
    * become significant if the processing of certain parameters has side effects (for example,
    * a user-defined function, which can do almost anything). This difference is a current
    * limitation of the FWD runtime.
    * 
    * @param   bufMgr
    *          {@code BufferManager} instance for the current user context.
    * @param   isFindByRowid
    *          A lambda expression which should return {@code true} if the current database operation
    *          represents a legacy FIND by RECID/ROWID, else {@code false}.
    * @param   suppressNestedQueryError
    *          {@code true} to suppress (i.e., handle as a warning only, but do not alter control flow) a
    *          nested query error; {@code false} to allow such an error to alter control flow by permitting
    *          an {@code ErrorConditionException} thrown during nested query detection to propagate upward
    *          from this method.
    * @param   args
    *          Array of query substitution arguments, or {@code null} if there are none.
    * 
    * @return  {@code true} if all arguments were resolved without error, {@code false} if any
    *          error occurred.
    * 
    * @throws  ErrorConditionException
    *          if a query is executed within the WHERE clause of another query and such an error is not
    *          suppressed.
    */
   protected static boolean preprocessSubstitutionArguments(BufferManager bufMgr,
                                                            Supplier<Boolean> isFindByRowid,
                                                            boolean suppressNestedQueryError,
                                                            Object[] args)
   {
      // verify that we are not processing a query within another query; if not in silent error
      // mode and we are nested, this will raise an error condition (unless the inner query is a FIND by
      // RECID/ROWID
      try
      {
         bufMgr.checkNestedQuery(isFindByRowid);
      }
      catch (ErrorConditionException exc)
      {
         if (!suppressNestedQueryError)
         {
            throw exc;
         }
         else
         {
            return false;
         }
      }
      
      if (args == null)
      {
         return true;
      }
      
      boolean success = true;
      
      ErrorManager.ErrorHelper errHlp = bufMgr.getTxHelper().errHlp;
      
      int len = args.length;
      boolean silent = len > 0 && errHlp.isSilent();
      
      for (int i = 0; i < len; i++)
      {
         Object next = args[i];
         
         final int idx = i;
         
         bufMgr.incrementDepth();
         boolean unknownModeRestore = bufMgr.setUnknownMode(true);
         
         // make sure we work with the proper value and not a proxy
         Function<Object, Object> unwrapProxy = (value) -> 
         {
            if (value instanceof BaseDataType && BaseDataType.isProxy((BaseDataType) value))
            {
               return ((BaseDataType) value).val();
            }
            return value;
         };
         
         try
         {
            Runnable parm = null;
            
            if (next instanceof P2JQuery.Parameter)
            {
               parm = () -> args[idx] = unwrapProxy.apply(((Parameter) next).resolve());
            }
            else if (next instanceof QueryParam && next instanceof Resolvable)
            {
               parm = () -> args[idx] = unwrapProxy.apply(((Resolvable) next).resolve());
            }
            
            if (parm != null)
            {
               if (!silent)
               {
                  // implicitly run in silent mode to mimic the 4GL's eating of errors while
                  // processing the where clause
                  success = !errHlp.silent(parm);
               }
               else
               {
                  try
                  {
                     // already in silent error mode; error may throw ErrorConditionException
                     parm.run();
                  }
                  catch (ErrorConditionException exc)
                  {
                     success = false;
                  }
               }
               
               if (bufMgr.isNestedQueryError())
               {
                  if (silent)
                  {
                     // this will be recorded, not thrown; it represents a weird error state after a nested
                     // query error is raised when running silent
                     errHlp.addError(0, "", true, false);
                  }
                  success = false;
               }
               
               if (!success)
               {
                  // clear pending error status -- we just need to eat the error and abort;
                  // this will not clear a 565 FIND error, which may happen after this point
                  errHlp.clearPending();
                  
                  // continue processing; the 4GL engine not showing errors for bad constructs:
                  // i.e. f1 = entry(...) will compare f1 with ? even if the entry is using a bad index
                  args[idx] = new unknown();
               }
            }
         }
         finally
         {
            bufMgr.setUnknownMode(unknownModeRestore);
            bufMgr.decrementDepth();
         }
      }
      
      // the processing was successful as the failed evaluation of arguments resulted in unknown values.
      // therefore, the query should still execute with unknown arguments
      return true;
   }
   
   /**
    * Translate this <code>sort</code> clause to use the bound DMO's alias and property names,
    * instead the definition (conversion-time) alias and property names.
    * 
    * @param    bound
    *           The runtime-bound buffer.
    * @param    definition
    *           The buffer as it was used to generate the <code>sort</code> clause at conversion
    *           time.
    * @param    sort
    *           The sort clause to be translated.
    *           
    * @return   The translated clause.
    */
   protected static String translateSort(RecordBuffer bound, RecordBuffer definition, String sort)
   {
      if (sort == null)
      {
         return null;
      }

      SortCacheKey sortCacheKey = new SortCacheKey(bound.getDMOAlias(),
                                                   definition.getDMOAlias(),
                                                   bound.getDMOInterface(),
                                                   definition.getDMOInterface(),
                                                   sort);
      String translatedSort;

      synchronized (sortCache)
      {
         translatedSort = sortCache.get(sortCacheKey);
      }

      if (translatedSort == null)
      {
         List<SortCriterion> defSort;
         try
         {
            defSort = SortCriterion.parse(sort, definition, false);
         }
         catch (PersistenceException e)
         {
            throw new RuntimeException(e);
         }

         translatedSort = translateSort(new RecordBuffer[]{bound},
                                        new RecordBuffer[]{definition},
                                        defSort);
         synchronized (sortCache)
         {
            sortCache.put(sortCacheKey, translatedSort);
         }
      }
      return translatedSort;
   }
   
   /**
    * Translate this <code>sort</code> clause to use the bound DMOs' alias and property names,
    * instead the definition (conversion-time) alias and property names.
    * 
    * @param    bound
    *           The runtime-bound buffers.
    * @param    definition
    *           The buffers as it was used to generate the <code>sort</code> clause at conversion
    *           time.
    * @param    defSort
    *           The sort clause to be translated as a list of {@code SortCriterion}.
    *           
    * @return   The translated clause.
    */
   protected static String translateSort(RecordBuffer[]      bound, 
                                         RecordBuffer[]      definition, 
                                         List<SortCriterion> defSort)
   {
      Map<String, Map<String, String>> mappingsByDefAlias = new HashMap<>();
      Map<String, String> boundAliasByDefAlias = new HashMap<>();
      
      for (int i = 0; i < definition.length; i++)
      {
         String defAlias = definition[i].getDMOAlias();
         mappingsByDefAlias.put(defAlias, definition[i].getBoundPropertyMappings());
         boundAliasByDefAlias.put(defAlias, bound[i].getDMOAlias());
      }
      
      StringBuilder boundSort = new StringBuilder();
      for (SortCriterion sc : defSort)
      {
         String alias;
         String prop;
         
         if (boundAliasByDefAlias.containsKey(sc.getAlias()))
         {
            prop = mappingsByDefAlias.get(sc.getAlias()).get(sc.getUnqualifiedName());
            alias = boundAliasByDefAlias.get(sc.getAlias());
         }
         else
         {
            prop = sc.getUnqualifiedName();
            alias = sc.getAlias();
         }
         
         String originalName = sc.getOriginalName();
         String subscript = "";
         int subscriptIndex = originalName.indexOf('[');
         if (subscriptIndex > 0)
         {
            subscript = originalName.substring(subscriptIndex, originalName.indexOf(']') + 1);
         }
         
         if (boundSort.length() != 0)
         {
            boundSort.append(", ");
         }
         boundSort.append(alias).append(".")
                  .append(prop)
                  .append(subscript)
                  .append(sc.isAscending() ? "" : " desc");
      }
      
      return boundSort.toString();
   }
   
   /**
    * Translate this <code>where</code> clause to use the bound DMO's alias and property names,
    * instead the definition (conversion-time) alias and property names.
    * 
    * @param    bound
    *           The runtime-bound buffers.
    * @param    definition
    *           The buffers as it was used to generate the <code>where</code> clause at conversion
    *           time.
    * @param    where
    *           The HQL to be translated.
    *           
    * @return   The translated HQL.
    */
   protected String translateWhere(List<RecordBuffer> bound, List<RecordBuffer> definition, String where)
   {
      if (where == null)
      {
         return null;
      }
      
      if (this.externalBuffers != null)
      {
         for (int i = 0; i < this.externalBuffers.length; i++)
         {
            bound.add(this.externalBuffers[i]);
            definition.add(this.externalBuffersDefs[i]);
         }
      }
      
      return FQLPreprocessor.translate(bound, definition, where, false);
   }
   
   /**
    * Check if the two buffers differ.
    * 
    * @param    buf
    *           The first buffer.
    * @param    defBuf
    *           The second buffer.
    *           
    * @return   <code>true</code> if the buffers are different instances or have different 
    *           backing DMO interface.
    */
   protected static boolean buffersMatch(RecordBuffer buf, RecordBuffer defBuf)
   {
      return buf == defBuf || 
             (buf.getDMOInterface() == defBuf.getDMOInterface() && 
              buf.getDMOAlias().equals(defBuf.getDMOAlias()));
   }
   
   /**
    * Return the {@link LockType} instance for the given internal lock and nowait with corresponding query
    * processing.
    * <p>
    *
    * @param   lock
    *          The lock type as an integer value (see lock type constants in {@code LockType}).
    * @param   nowait
    *          The wait type as an integer value (see wait type constants in {@code LockType}).
    *
    * @return  {@code LockType} instance.
    *
    * @throws  ErrorConditionException
    *          If the 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 = "The first argument to QUERY GET methods must be NO-LOCK, SHARE-LOCK or EXCLUSIVE-LOCK";
            num = 7314;
         }
         else
         {
            err = "QUERY GET second argument must be NO-WAIT or 0";
            num = 7360;
         }
         ErrorManager.recordOrThrowError(num, err);
      }
      return lockType;
   }
   
   /**
    * Set this query in a 'dynamic' mode, to mark the fact that it was used as a dynamic delegate in a query
    * resource.
    * 
    * @param    dynamic
    *           Flag indicating that this is a dynamic delegate for a query resource.
    */
   @Override
   public void setDynamicPredicate(boolean dynamic)
   {
      this.dynamicPredicate = dynamic;
   }
   
   /**
    * Check if this query is originating from a dynamic predicate.
    * 
    * @return   The {@link #dynamicPredicate} value.
    */
   @Override
   public boolean isDynamicPredicate()
   {
      return dynamicPredicate;
   }
   
   /**
    * Enable results list scrolling/repositioning.  This default
    * implementation does nothing.
    */
   public void setScrolling()
   {
   }
   
   /**
    * Set the behavior of this query as a record retrieval request moves off
    * the end of its results.  If set to lenient mode, invocations of
    * <code>next</code> and <code>previous</code> which do not find a record
    * do not raise a <code>QueryOffEndException</code>.  By default, this
    * would normally raise the exception.
    *
    * @param   lenientOffEnd
    *          <code>true</code> to suppress <code>QueryOffEndException</code>
    *          for <code>next</code> and <code>previous</code> requests;
    *          <code>false</code> to permit these exceptions.
    */
   public void setLenientOffEnd(boolean lenientOffEnd)
   {
      this.lenientOffEnd = lenientOffEnd;
   }
   
   
   /**
    * Indicate whether this query will throw a
    * <code>QueryOffEndException</code> when it runs off the end of its
    * results.
    *
    * @return  <code>true</code> if an exception will be thrown, else
    *          <code>false</code>.
    */
   protected boolean isLenientOffEnd()
   {
      return lenientOffEnd;
   }
   
   /**
    * Enforce a database join if possible. By default, multi-table queries rely on 
    * algorithm to decide whether a database join is optimal or not. With such setter,
    * the database join is always preferred.
    * 
    * @param   forceDatabaseJoin
    *          {@code true} if the query should always use DB joins.
    */
   @Override
   public void setForceDatabaseJoin(logical forceDatabaseJoin)
   {
      this.forceDatabaseJoin = forceDatabaseJoin.booleanValue();
   }
   
   /**
    * Check if the database join enforcing is active.
    * 
    * @return   {@code true} if the query will always use DB joins.
    */
   @Override
   public logical isForceDatabaseJoin()
   {
      return logical.of(this.forceDatabaseJoin);
   }
   
   /**
    * This flag is used in order to allow reposition to unflushed records (ones
    * which were not in the database when the query was opened). This can be set
    * only when the query is scrolling. When a reposition to an unflushed record
    * is made, the result list is reset.
    * This method must be invoked before any results are retrieved.
    */
   @Override
   public void setIndexedReposition()
   {
      indexedReposition = true;
   }
   
   /**
    * Disable indexed reposition optimization to disallow repositioning to
    * unflushed records. 
    * This method must be invoked before any results are retrieved. 
    */
   @Override
   public boolean isIndexedReposition()
   {
      return indexedReposition;
   }
   
   
   /**
    * Reposition the prepared query to the first result if the query is being
    * browsed.
    */
   public void open()
   {
      closed = false;
      
      if (browsed)
      {
         reposition(1);
      }
   }
   
   /**
    * Indicate whether the cursor has run off one or the other end of the
    * associated query's list of available results.  If <code>true</code>,
    * this indicates that the query cannot produce any more results in the
    * current scroll direction.
    *
    * @return  <code>true</code> if the cursor is off either end of its
    *          query's results list.
    */
   public logical isOffEnd()
   {
      boolean result = _isOffEnd();
      
      return (logical.of(result));
   }
   
   /**
    * Add an accumulator to this query.  The accumulator expects to be
    * notified whenever a new result row is accessed (during sequential row
    * access).
    * <p>
    * This implementation assumes the accumulator is not associated with a
    * break group.
    *
    * @param   accum
    *          Accumulator to be registered with this query.
    */
   public void addAccumulator(Accumulator accum)
   {
      addAccumulator(accum, true);
   }
   
   
   
   /**
    * Add an accumulator to this query.  The accumulator expects to be notified whenever a new result row is
    * accessed (during sequential row access).
    * <p>
    * This implementation assumes the accumulator is not associated with a break group.
    *
    * @param   accum
    *          Accumulator to be registered with this query.
    * @param   deferred
    *          If {@code true}, calls to {@link #accumulate()} will not cause the accumulator to recalculate
    *          immediately, but instead to update the accumulator's state such that the next invocation of
    *          {@link Accumulator#accumulate()} will trigger the actual accumulation update.
    *          If {@code false}, the accumulator recalculates its internal state immediately.
    */
   public void addAccumulator(Accumulator accum, boolean deferred)
   {
      if (accumulators == null)
      {
         // Use sequential map to preserve add order.
         accumulators = new LinkedHashMap<>();
      }
      
      accumulators.put(accum, deferred ? Boolean.TRUE : Boolean.FALSE);
   }
   
   /**
    * Is this query set to fetch the next record after a reposition?
    *
    * @return  <code>true</code> if set to fetch after reposition, else
    *          <code>false</code>.
    */
   public boolean isFetchOnReposition()
   {
      return fetchOnReposition;
   }
   
   /**
    * Set this query to automatically fetch the next record after a
    * reposition, or turn this feature off.
    *
    * @param   enable
    *          <code>true</code> to enable automatic fetching;
    *          <code>false</code> to disable.
    */
   public void setFetchOnReposition(boolean enable)
   {
      fetchOnReposition = enable;
   }
   
   /**
    * Adds a listener to be notified when this query is open.
    * <p> 
    * Note that this method is only called for dynamic queries only (for the moment).
    *
    * @param   listener
    *          The listener to be notified.
    */
   @Override
   public void addQueryEventListener(QueryEventListener listener)
   {
      if (eventListeners == null)
      {
         eventListeners = new LinkedList<>();
      }
   
      eventListeners.add(listener);
   }
   
   /**
    * Notifies the registered QUERY-OPEN listeners that the event is about to happen.
    *  
    * @param    evaluate
    *           Flag indicating that the dynamic query must evaluate its dynamic calls (like DYNAMIC-FUNCTION)
    *           in the WHERE clause. 
    */
   @Override
   public void notifyQueryOpenListeners(boolean evaluate)
   {
      if (eventListeners != null)
      {
         Iterator<QueryEventListener> iter = eventListeners.iterator();
         while (iter.hasNext())
         {
            iter.next().onQueryOpen(evaluate);
         }
      }
   }
   
   /**
    * Notifies the registered QUERY-CLOSE listeners that the event is about to happen.
    */
   @Override
   public void notifyQueryCloseListeners()
   {
      if (eventListeners != null)
      {
         Iterator<QueryEventListener> iter = eventListeners.iterator();
         while (iter.hasNext())
         {
            iter.next().onQueryClose();
         }
      }
   }
   
   /**
    * Register a listener to receive notifications reposition events for this
    * query.
    *
    * @param   listener
    *          Reposition listener to be registered.
    */
   public void addRepositionListener(RepositionListener listener)
   {
      if (repoRegistry == null)
      { 
         repoRegistry = new WeakList<>();
      }
      
      repoRegistry.add(listener);
   }
   
   /**
    * Unregister a listener that was receiving notifications on reposition events for this
    * query.
    *
    * @param   listener
    *          Reposition listener to be unregistered.
    */
   public void removeRepositionListener(RepositionListener listener)
   {
      if (repoRegistry != null)
      {
         repoRegistry.remove(listener);
      }
   }
   
   /**
    * Indicate to this query that whether it is associated with a browse widget or not.
    *
    * @param   browsed
    *          {@code true} to indicate the query has an associated browse widget, else {@code false}.
    */
   public void setBrowsed(boolean browsed)
   {
      this.browsed = browsed;
      if (browsed)
      {
         skipDeletedRecord = false;
      }
   }
   
   /**
    * Informs the query that the next operations will be performed for filling browse rows, so it should not
    * return deleted rows/records (if argument is {@code true}). That is, temporarily overwrite the
    * SKIP-DELETED-RECORDS until the flag is turned off.
    *
    * @param   on
    *          if {@code true} the deleted rows will not be returned because the query is managed by the
    *          browse widget.
    */
   @Override
   public void setFillingBrowseRows(boolean on)
   {
      fillingBrowseRows = on;
   }
   
   /**
    * Set the forward-only attribute to the given value.
    * 
    * @param   value
    *          <code>true</code> to enable forward-only, <code>false</code> to disable it.
    */
   @Override
   public void setForwardOnly(boolean value)
   {
      forwardOnly = value;
   }
   
   /**
    * Method that returns the value of the forwardOnly flag.
    * 
    * @return  <code>true</code> if forward-only is enabled, <code>false</code> otherwise.
    */
   @Override
   public boolean isForwardOnly()
   {
      return forwardOnly;
   }
   
   /**
    * Notify all registered listeners that query is repositioned or closed.
    * 
    * @param   closed
    *          A <code>true</code> value will inform all listeners that the query was explicitly
    *          closed.
    * @param   error
    *          <code>true</code> if an error happened during reposition.
    * @param   targetRepositionRow
    *          Row used as the target for reposition. If it was deleted, reposition with fetching
    *          may end up on a different row.
    */
   @Override
   public void notifyRepositionListeners(boolean closed, boolean error, int targetRepositionRow)
   {
      if (repoRegistry != null)
      {
         Iterator<RepositionListener> iter = repoRegistry.iterator();
         while (iter.hasNext())
         {
            RepositionListener next = iter.next();
            if (closed)
            {
               next.queryClosed();
            }
            else
            {
               next.queryRepositioned(error, targetRepositionRow);
            }
         }
      }
   }
   
   /**
    * Indicate whether this query stands alone, or is wrapped by a query 
    * wrapper.    
    *  
    * @return  <code>true</code> if standalone, else <code>false</code>.
    */
   public boolean isStandalone()
   {
      return standalone;
   }
   
   /**
    * Change the standalone nature of this query.  A query is standalone by
    * default, but can be changed to non-standalone by the wrapper which 
    * contains it.
    * 
    * @param   standalone
    *          <code>true</code> to set query as standalone;
    *          <code>false</code> to set query as contained.
    */
   public void setStandalone(boolean standalone)
   {
      this.standalone = standalone;
   }
   
   /**
    * Clean up resources associated with this query when its scope ends.
    * <p>
    * This default implementation does nothing.
    */
   public void cleanup()
   {
   }
   
   /**
    * Explicitly close the prepared query.
    * <p>
    * This default implementation calls {@link #cleanup()}.
    */
   public void close()
   {
      cleanup();
      closed = true;
   }
   
   /**
    * Sets the left-outer-join property for this query.
    *
    * @param   outer
    *          {@code true} iif this is a left outer join component of the query.
    */
   public void setOuter(boolean outer)
   {
      this.outer = outer;
   }
   
   /**
    * Indicate whether the query is of presort type.
    *
    * @return  <code>true</code> if the query is of presort type..
    */
   public boolean isPresort()
   {
      return presort;
   }
   
   /**
    * Indicate whether the query is of presort type.
    *
    * @param   presort
    *          <code>true</code> indicating that a query of presort type.
    *          <code>false</code> is not of presort type.
    */
   public void setPresort(boolean presort)
   {
      this.presort = presort;
   }
   
   /**
    * Indicate whether the query is defined inside the scope of FOR EACH block.
    *
    * @param   iterating
    *          <code>true</code> indicating that a query is inside FOR EACH block;
    *          <code>false</code> is defined outside of a FOR EACH.
    */
   public void setIterating(boolean iterating)
   {
      this.iterating = iterating;
   }
     
   /**
    * Indicate whether the query is defined inside the scope of FOR EACH block.
    *
    * @return  <code>true</code> if the query was defined inside a FOR EACH block.
    */
   public boolean isIterating()
   {
      return iterating;
   }
   
   /**
    * Set a mode such that setting a null record into a backing buffer may or
    * may not result in an error condition.  This applies only to requests to
    * retrieve <code>FIRST</code>, <code>LAST</code>, and <code>UNIQUE</code>
    * records.
    *
    * @param   errorIfNull
    *          If <code>true</code>, setting a <code>null</code> value as the
    *          current record in an underlying buffer will raise an error
    *          condition (if not in silent error mode); if <code>false</code>,
    *          this action will raise an end condition instead.
    */
   public void setErrorIfNull(boolean errorIfNull)
   {
      this.errorIfNull = errorIfNull;
   }
   
   /**
    * Report whether this query is associated with a browse widget.
    *
    * @return  <code>true</code> if associated with a browse, else
    *          <code>false</code>.
    */
   public boolean isBrowsed()
   {
      return browsed;
   }
   
   /**
    * Add one or more buffers to the list of those which this query references externally. These
    * typically will be buffers referenced by converted CAN-FIND expressions embedded in the
    * query's FQL expression, which are not the primary buffers associated with the query.
    * 
    * @param   dmos
    *          One or more external buffers.
    * 
    * @return  This query instance.
    */
   public P2JQuery addExternalBuffers(DataModelObject... dmos)
   {
      int len;
      if (dmos != null && (len = dmos.length) > 0)
      {
         int offset = 0;
         
         if (externalBuffers != null)
         {
            offset = externalBuffers.length;
            RecordBuffer[] list = new RecordBuffer[offset + len];
            RecordBuffer[] listDef = new RecordBuffer[offset + len];
            for (int i = 0; i < offset; i++)
            {
               list[i] = externalBuffers[i].buffer();
               listDef[i] = externalBuffersDefs[i].definition();
            }
            externalBuffers = list;
            externalBuffersDefs = listDef;
         }
         else
         {
            externalBuffers = new RecordBuffer[len];
            externalBuffersDefs = new RecordBuffer[len];
         }
         
         for (int i = 0; i < len; i++)
         {
            externalBuffers[i + offset] = ((BufferReference) dmos[i]).buffer();
            externalBuffersDefs[i + offset] = ((BufferReference) dmos[i]).definition();
         }
      }
      
      return this;
   }
   
   /**
    * Get an array of all record buffers referenced by this query, including those directly
    * involved in the query, as well as those (if any) indirectly referenced by inlined,
    * converted, CAN-FIND expressions, and that which represents the inverse buffer of a natural
    * join.
    * 
    * @return  All record buffers referenced by this query.
    */
   public RecordBuffer[] getReferencedBuffers()
   {
      RecordBuffer[] qbufs = getRecordBuffers();
      
      AbstractJoin join = getJoin();
      
      if (externalBuffers == null && join == null)
      {
         return qbufs;
      }
      
      int len = qbufs.length;
      
      if (externalBuffers != null)
      {
         len += externalBuffers.length;
      }
      
      if (join != null)
      {
         len++;
      }
      
      RecordBuffer[] all = new RecordBuffer[len];
      
      int limit = qbufs.length;
      
      int i = 0;
      while (i < limit)
      {
         all[i] = qbufs[i];
         i++;
      }
      
      if (externalBuffers != null)
      {
         int start = i;
         limit += externalBuffers.length;
         while (i < limit)
         {
            all[i] = externalBuffers[i - start];
            i++;
         }
      }
      
      if (join != null)
      {
         all[i] = join.getInverse().buffer();
      }
      
      return all;
   }
   
   /**
    * Retrieve the context-local error helper used for silent mode operations.
    * 
    * @return  The context-local error helper used for silent mode operations.
    */
   protected ErrorHelper getErrorHelper()
   {
      if (errorHelper == null)
      {
         errorHelper = ErrorManager.getErrorHelper();
      }
      
      return errorHelper;
   }
   
   /**
    * Get the default lock type for this query.
    * 
    * @return   {@link LockType#NONE} if this is a dynamic delegate for a query resource, or
    *           {@link LockType#SHARE} otherwise.
    */
   protected LockType getDefaultLock()
   {
      return dynamicPredicate ? LockType.NONE: LockType.SHARE;
   }
   
   /**
    * Get the external buffers, if any, associated with this query.
    * 
    * @return  Array of external buffers or {@code null}.
    */
   protected RecordBuffer[] getExternalBuffers()
   {
      return externalBuffers;
   }
   
   /**
    * Get the external buffers definitions, if any, associated with this query.
    * 
    * @return  Array of external buffers definitions or {@code null}.
    */
   protected RecordBuffer[] getExternalBuffersDefs()
   {
      return externalBuffersDefs;
   }
   
   /**
    * Indicate whether there was a fatal error during query substitution parameter processing
    * which should abort this query at the earliest appropriate time.
    * 
    * @return  {@code true} if there was a query substitution parameter processing error, else
    *          {@code false}.
    */
   protected boolean isFatalError()
   {
      return fatalError;
   }
   
   /**
    * Remember there was a fatal error during query substitution parameter processing and that
    * this query should abort at the earliest appropriate time.
    */
   protected void setFatalError()
   {
      fatalError = true;
   }
   
   /**
    * Indicate whether failed <code>FIRST</code>, <code>LAST</code>, and
    * <code>UNIQUE</code> retrieval attempts should be considered an error
    * condition.
    *
    * @return  <code>true</code> if failed first/last/unique retrievals
    *          should be considered an error;  <code>false</code> is such
    *          failures should be considered a query off-end condition.
    */
   protected boolean isErrorIfNull()
   {
      return errorIfNull;
   }
   
   /**
    * Determine whether DMO property references in this query's FQL should be unqualified by a
    * DMO alias name.
    * 
    * @return  {@code true} if the DMO alias should be omitted from property references, else
    *          {@code false}.
    */
   protected boolean isOmitAlias()
   {
      return omitAlias;
   }
   
   /**
    * Specify whether DMO property references in this query's FQL should be unqualified by a DMO
    * alias name.
    * 
    * @param   omitAlias
    *          {@code true} if the DMO alias should be omitted from property references, else
    *          {@code false}.
    */
   protected void setOmitAlias(boolean omitAlias)
   {
      this.omitAlias = omitAlias;
   }

   /**
    * This method should be called by subclasses after any reposition of the
    * query.  If the query is set to fetch a record on reposition events, it
    * does so now (using the {@link #next} method).  If any listeners are
    * registered to receive notifications of reposition events, they are
    * notified now.
    *
    * @param   permitFetch
    *          <code>true</code> to permit immediate record fetch;
    *          <code>false</code> to override <code>fetchOnReposition</code>
    *          flag and explicitly disallow immediate record fetch.
    */
   protected void afterReposition(boolean permitFetch)
   {
      afterReposition(permitFetch, !permitFetch);
   }
   
   /**
    * This method should be called by subclasses after any reposition of the
    * query.  If the query is set to fetch a record on reposition events, it
    * does so now (using the {@link #next} method).  If any listeners are
    * registered to receive notifications of reposition events, they are
    * notified now.
    *
    * @param   permitFetch
    *          <code>true</code> to permit immediate record fetch;
    *          <code>false</code> to override <code>fetchOnReposition</code>
    *          flag and explicitly disallow immediate record fetch.
    * @param   error
    *          <code>true</code> if error happened during reposition.
    */
   protected void afterReposition(boolean permitFetch, boolean error)
   {
      if (ignoreAfterRepo)
      {
         return;
      }
      
      integer row = currentRow();
      int targetRepositionRow = row.isUnknown() ? -1 : row.intValue() - 1;
      
      if (permitFetch && fetchOnReposition)
      {
         if (isBrowsed())
         {
            next(LockType.NONE);
         }
         else
         {
            loadRowAtCursor();
         }
      }
      
      // Prevent recursive notifications.
      if (isRepositionNotificationActive())
      {
         return;
      }
      
      notificationActive = true;
      notifyRepositionListeners(false, error, targetRepositionRow);
      notificationActive = false;
   }
   
   /**
    * This method should be called by subclasses after any reposition of the
    * query.  If the query is set to fetch a record on reposition events, it
    * does so now (using the {@link #next} method).  If any listeners are
    * registered to receive notifications of reposition events, they are
    * notified now.<p>
    * This method also brings the query to the required state if reposition
    * has failed.
    *
    * @param   permitFetch
    *          <code>true</code> to permit immediate record fetch;
    *          <code>false</code> to override <code>fetchOnReposition</code>
    *          flag and explicitly disallow immediate record fetch.
    * @param   oldCurrentRow
    *          Current row at the start of the reposition. Can be <code>unknown</code>.
    */
   protected void afterReposition(boolean permitFetch, integer oldCurrentRow)
   {
      if (!permitFetch)
      {
         // error during reposition
         integer backupRow = isBrowsed() ? oldCurrentRow : new integer(MathOps.plus(size(), 1));
         if (!backupRow.isUnknown())
         {
            ignoreAfterRepo = true;
            try
            {
               reposition(backupRow);
            }
            finally
            {
               ignoreAfterRepo = false;
            }
         }
         
         if (isBrowsed())
         {
            afterReposition(true, true);
         }
         else
         {
            releaseBuffers();
            afterReposition(false);
         }
      }
      else
      {
         afterReposition(true);
      }
   }

   /**
    * Indicate if we are currently notifying listeners of a reposition event.
    * 
    * @return  <code>true</code> if a reposition notification is active, else
    *          <code>false</code>.
    */
   protected boolean isRepositionNotificationActive()
   {
      return notificationActive;
   }
   
   /**
    * Synchronize the query with the cursor by causing the query to load the
    * current row's results (if any) into its backing buffer(s).
    * <p>
    * This implementation does nothing;  it should be overridden to implement
    * this behavior as needed.
    */
   protected void loadRowAtCursor()
   {
   }
   
   /**
    * Notify accumulators attached to this query (if any) that a new result
    * row has been accessed, and that they should update their internal state.
    * Subclasses should invoke this method whenever an iteration of a query
    * result occurs, such as on the retrieval of the first, last, next, or
    * previous result row.
    */
   protected final void accumulate()
   {
      if (!hasAccumulators())
      {
         return;
      }
      
      for (Map.Entry<Accumulator, Boolean> next : accumulators.entrySet())
      {
         Accumulator accum = next.getKey();
         Boolean deferred = next.getValue();
         
         accumulate(accum, deferred);
      }
   }
   
   /**
    * Check if query has attached accumulators.
    * 
    * @return  <code>true</code> if accumulators attached to the query.
    */
   protected boolean hasAccumulators()
   {
      return accumulators != null && !accumulators.isEmpty();
   }
   
   /**
    * Subclasses which support the concept of break groups in query results
    * should override this method to report whether the current result row
    * being visited represents a new break group, for the given break group
    * key.
    *
    * @param   key
    *          Resolvable object which is used as the key to identify the
    *          break group category being tested.
    *
    * @return  <code>true</code> if the current result represents the first
    *          entry in a new break group;  else <code>false</code>.  This
    *          default implementation always returns <code>false</code>.
    */
   protected boolean isNewBreakGroup(Resolvable key)
   {
      return false;
   }
   
   /**
    * Determine whether the query result row currently being visited is the
    * last row within a break group whose category is identified by the
    * specified resolvable object.
    *
    * @param   key
    *          Break group category key.  May be <code>null</code> (which
    *          should always trigger a <code>false</code> return).
    *
    * @return  <code>false</code>;  subclasses should override this method if
    *          a different result is required.
    */
   protected boolean isLastOfBreakGroup(Resolvable key)
   {
      return false;
   }
   
   /**
    * Prepare to perform a fetch.  This involves checking for a pending error
    * in the error manager and releasing the current buffer record if an
    * error is pending.  Releasing the record may result in an exception
    * being thrown by the record buffer.
    *
    * @return  <code>false</code> if an error is pending, else
    *          <code>true</code>.
    */
   protected boolean prepareFetch()
   {
      if (fatalError)
      {
         try
         {
            releaseBuffers();
         }
         catch (QueryOffEndException exc)
         {
            // intentionally ignored
         }
         
         if (LOG.isLoggable(Level.FINE))
         {
            String msg = "Fatal error aborted query: " + ErrorManager.getErrorText(1).toStringMessage();
            LOG.log(Level.FINE, msg, new ErrorConditionException(msg));
         }
         
         return false;
      }
      
      return true;
   }
   
   /**
    * Default, no-op implementation which should be overridden by subclasses
    * which need to release the current record of each of their buffers in
    * response to a fetch request while an error is pending in the error
    * manager.
    */
   public void releaseBuffers()
   {
   }
   
   /**
    * Invoke the appropriate <code>iterate</code> method variant on the
    * specified accumulator.  If the accumulator manages only a primary
    * statistic (i.e., no break groups have been added), the {@link
    * com.goldencode.p2j.util.Accumulator#iterate(boolean) simple form}
    * is invoked.  If break groups have been added to the accumulator, the
    * {@link com.goldencode.p2j.util.Accumulator#iterate(java.util.Map,
    * boolean) more complex form} is invoked.
    *
    * @param   accum
    *          Target accumulator object.
    * @param   deferred
    *          <code>true</code> if the accumulator was registered to this
    *          query as a deferred accumulator, else <code>false</code>.
    *
    * @see     #addAccumulator
    */
   private void accumulate(Accumulator accum, boolean deferred)
   {
      Iterator<Resolvable> iter = accum.breakGroupKeys();
      
      // Iterate the accumulator.
      if (!iter.hasNext())
      {
         accum.iterate(deferred);
      }
      else
      {
         Map<Resolvable, Boolean> breakMap = new HashMap<>();
         while (iter.hasNext())
         {
            Resolvable key = iter.next();
            Boolean b = (isNewBreakGroup(key) ? Boolean.TRUE : Boolean.FALSE);
            breakMap.put(key, b);
         }
         accum.iterate(breakMap, deferred);
      }
      
      // Update the accumulator's "last break group" state.
      Resolvable key = accum.getFirstBreakGroupKey();
      accum.setLastOfGroup(isLastOfBreakGroup(key));
   }

   /**
    * Conversion of GET-BUFFER-HANDLE() method (KW_GET_BUFH).
    *
    * Returns a handle of the first buffer of a query object.
    *
    * @return buffer handle
    */
   public handle bufferHandle()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of GET-BUFFER-HANDLE() method (KW_GET_BUFH).
    *
    * Returns a handle of a particular buffer of a query object.
    *
    * @param   bufferName
    *          buffer-name. A CHARACTER expression that evaluates to the name
    *          of a buffer in the query or DataSet object.
    *
    * @return buffer handle
    */
   public handle bufferHandle(String bufferName)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of GET-BUFFER-HANDLE() method (KW_GET_BUFH).
    *
    * Returns a handle of a particular buffer of a query object.
    *
    * @param   bufferName
    *          buffer-name. A CHARACTER expression that evaluates to the name
    *          of a buffer in the query or DataSet object.
    *
    * @return buffer handle
    */
   public handle bufferHandle(character bufferName)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of GET-BUFFER-HANDLE() method (KW_GET_BUFH).
    *
    * Returns a handle of a particular buffer of a query object.
    *
    * @param   bufferSequenceNumber
    *          buffer-sequence-number. An INTEGER that represents the sequence
    *          number of the desired buffer.
    *
    * @return buffer handle
    */
   public handle bufferHandle(int bufferSequenceNumber)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of GET-BUFFER-HANDLE() method (KW_GET_BUFH).
    *
    * Returns a handle of a particular buffer of a query object.
    *
    * @param   bufferSequenceNumber
    *          buffer-sequence-number. An INTEGER that represents the sequence
    *          number of the desired buffer.
    *
    * @return buffer handle
    */
   @Override
   public handle bufferHandle(int64 bufferSequenceNumber)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of QUERY-CLOSE() method (KW_QRY_CLOS).
    *
    * Closes a query object.
    *
    * @return  <code>true</code> if QUERY-CLOSE() succeeds,
    *          else <code>false</code>.
    */
   public logical queryClose()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-BACKWARD() method (KW_REPOS_B).
    *
    * Moves a query object's result list pointer backward a particular number
    * of rows.
    *
    * @return  <code>true</code> if REPOSITION-BACKWARD() succeeds,
    *          else <code>false</code>.
    */
   public logical queryBackward(NumberType rows)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-BACKWARD() method (KW_REPOS_B).
    *
    * Moves a query object's result list pointer backward a particular number
    * of rows.
    *
    * @return  <code>true</code> if REPOSITION-BACKWARD() succeeds,
    *          else <code>false</code>.
    */
   public logical queryBackward(int rows)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-FORWARD() method (KW_REPOS_F).
    *
    * Moves a query object's result list pointer forward a particular number
    * of rows.
    *
    * @return  <code>true</code> if REPOSITION-FORWARD() succeeds,
    *          else <code>false</code>.
    */
   public logical queryForward(NumberType rows)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-FORWARD() method (KW_REPOS_F).
    *
    * Moves a query object's result list pointer forward a particular number
    * of rows.
    *
    * @return  <code>true</code> if REPOSITION-FORWARD() succeeds,
    *          else <code>false</code>.
    */
   public logical queryForward(int rows)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-TO-ROW() method (KW_REPOS_2R).
    *
    * Moves a query object's result list pointer to the row corresponding to the specified
    * sequence number.
    *
    * @param   n
    *          An integer expression representing the sequence number.
    *
    * @return  <code>true</code> if REPOSITION-TO-ROW() succeeds,
    *          else <code>false</code>.
    */
   public logical queryReposition(int n)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-TO-ROW() method (KW_REPOS_2R).
    *
    * Moves a query object's result list pointer to the row corresponding to the specified
    * sequence number.
    *
    * @param   n
    *          An integer expression representing the sequence number.
    *
    * @return  <code>true</code> if REPOSITION-TO-ROW() succeeds,
    *          else <code>false</code>.
    */
   public logical queryReposition(NumberType n)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of REPOSITION-TO-ROWID() method (KW_REPOS_2I).
    *
    * Reposition the cursor such that a request to retrieve the next result
    * will retrieve the result which matches the specified array of primary
    * key IDs.  A request to retrieve the previous result will retrieve the
    * result immediately previous to this, if any, in the results list.
    *
    * @param   id1
    *          A primary key ID, representing the target record (in the case
    *          of a join, this ID is associated with the left-most record in
    *          the join).
    * @param   joinIDs
    *          All remaining primary key IDs, if any, arranged from left to
    *          right to coincide with the records being joined by the
    *          underlying query.
    *
    * @return  <code>true</code> if REPOSITION-TO-ROWID() succeeds,
    *          else <code>false</code>.
    */
   public logical queryRepositionByID(rowid id1, rowid...joinIDs)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }
   
   /**
    * Conversion of REPOSITION-TO-ROWID() method (KW_REPOS_2I).
    *
    * Reposition the cursor such that a request to retrieve the next result
    * will retrieve the result which matches the specified array of primary
    * key IDs.  A request to retrieve the previous result will retrieve the
    * result immediately previous to this, if any, in the results list.
    *
    * @param   id1
    *          A primary key ID, representing the target record (in the case
    *          of a join, this ID is associated with the left-most record in
    *          the join).
    * @param   joinIDs
    *          The list of IDs, starting with the primary key ID representing the target record, while 
    *          all remaining primary key IDs, if any, arranged from left to right to coincide with the records 
    *          being joined by the underlying query.
    *
    * @return  <code>true</code> if REPOSITION-TO-ROWID() succeeds,
    *          else <code>false</code>.
    */
   public logical queryRepositionByID(rowid[] joinIDs)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of NUM-RESULTS attribute (KW_NUM_RES).
    *
    * Getter of NUM-RESULTS attribute
    *
    */
   public integer getNumResults()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Adds one new buffer to a query object or dynamic DataSet object,
    * without affecting the other buffers, if any. Use the SET-BUFFERS( )
    * method to remove all prior buffers and set all buffers for the object
    * at the same time.
    *
    * @param   bufHandle
    *          A buffer handle to add.
    *
    * @return  true on success
    */
   public logical addBuffer(handle bufHandle)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Adds one new buffer to a query object or dynamic DataSet object, 
    * without affecting the other buffers, if any. Use the SET-BUFFERS( )
    * method to remove all prior buffers and set all buffers for the object 
    * at the same time.
    * 
    * @param   buf
    *          A buffer to add.
    *
    * @return  true on success
    */
   public logical addBuffer(Buffer buf)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Sets buffers for the query. Any buffers previously added
    * or set are removed. Use the <code>addBuffer()</code> method to add one 
    * buffer to the object, without affecting the other buffers, if any.
    * This method is the equivalent of <code>SET-BUFFERS()</code> method 
    * from 4GL.
    * 
    * @param   hBuffers
    *          Variable argument list of buffer handles to be set.
    *          In 4GL, the maximum number of buffers per query is 18.
    * 
    * @return  true on success
    */
   public logical setBuffers(handle ... hBuffers)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Sets buffers for the query. Any buffers previously added
    * or set are removed. Use the <code>addBuffer()</code> method to add one 
    * buffer to the object, without affecting the other buffers, if any.
    * This method is the equivalent of <code>SET-BUFFERS()</code> method 
    * from 4GL.
    * 
    * @param   bufs
    *          Variable argument list of Buffer to be set.
    *          In 4GL, the maximum number of buffers per query is 18.
    * 
    * @return  true on success
    */
   public logical setBuffers(Buffer ... bufs)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Compiles a predicate (query condition). The query must be open after this call.
    * This method is the equivalent of <code>QUERY-PREPARE()</code> method from 4GL.
    *  
    * @param   predicate
    *          The predicate to be prepared by this query. The syntax of the string is the same
    *          of the <code>OPEN QUERY</code> statement of the 4GL.
    * 
    * @return  true on success
    */
   public logical prepare(String predicate)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Compiles a predicate (query condition). The query must be open after this call.
    * This method is the equivalent of <code>QUERY-PREPARE()</code> method from 4GL.
    *  
    * @param   predicate
    *          The predicate to be prepared by this query. The syntax of the string is the same
    *          of the <code>OPEN QUERY</code> statement of the 4GL.
    * 
    * @return  true on success
    */
   public logical prepare(character predicate)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Opens a query object. 
    * This method is the equivalent of <code>QUERY-OPEN()</code> method from 4GL.
    * <u>Note</u>: You must perform <code>prepare()</code> on a query 
    * object before you perform <code>queryOpen</code> on it. 
    * 
    * @return  true on success
    */
   public logical queryOpen()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of INDEX-INFORMATION attribute (KW_IDX_INFO).
    *
    * Getter of INDEX-INFORMATION attribute
    *
    * @return A character string consisting of a comma-separated list of the index or
    *         indexes the query uses at the level of join specified.
    */
   public character indexInformation()
   {
      return indexInformation(new integer(1));
   }

   /**
    * Conversion of INDEX-INFORMATION attribute (KW_IDX_INFO).
    *
    * Getter of INDEX-INFORMATION attribute
    *
    * @param n
    * An integer expression that evaluates to the level of join for which you want
    * index information.

    * @return A character string consisting of a comma-separated list of the index or
    *         indexes the query uses at the level of join specified.
    */
   public character indexInformation(int n)
   {
      return indexInformation(new integer(n));
   }

   /**
    * Conversion of INDEX-INFORMATION attribute (KW_IDX_INFO).
    *
    * Getter of INDEX-INFORMATION attribute
    *
    * @param    n
    *           An integer expression that evaluates to the level of join for which you want index
    *           information.
    *
    * @return   A character string consisting of a comma-separated list of the index or
    *           indexes the query uses at the level of join specified.
    */
   public character indexInformation(NumberType n)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of FORWARD-ONLY attribute (KW_FWD_ONLY).
    *
    * Getter of FORWARD-ONLY attribute.
    *
    * @return current value of FORWARD-ONLY attribute.
    */
   public logical forwardOnly()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of FORWARD-ONLY attribute (KW_FWD_ONLY).
    *
    * Setter of FORWARD-ONLY attribute.
    *
    * @param  forwardOnly
    *         New value of FORWARD-ONLY attribute.
    */
   public void changeForwardOnly(logical forwardOnly)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of FORWARD-ONLY attribute (KW_FWD_ONLY).
    *
    * Setter of FORWARD-ONLY attribute.
    *
    * @param  forwardOnly
    *         New value of FORWARD-ONLY attribute.
    */
   public void changeForwardOnly(boolean forwardOnly)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of PREPARE_STRING attribute (KW_PREP_STR).
    *
    * Getter of PREPARE_STRING attribute.
    *
    * @return current value of PREPARE_STRING attribute.
    */
   public character prepareString()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Getter for the CACHE attribute.
    * 
    * @return   See above.
    */
   @Override
   public integer getCache()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }
   
   /**
    * Setter for the CACHE attribute.
    * 
    * @param    n
    *           The cache value.
    */
   @Override
   public void setCache(integer n)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }
   
   /**
    * Setter for the CACHE attribute.
    * 
    * @param    n
    *           The cache value.
    */
   @Override
   public void setCache(long n)
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of IS-OPEN attribute (KW_IS_OPEN).
    *
    * Getter of IS-OPEN attribute.
    *
    * @return current value of IS-OPEN attribute.
    */
   public logical isOpen()
   {
      throw new RuntimeException("Must use the equivalent QueryWrapper API!");
   }

   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT), which returns a Java boolean, for internal usage.
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>.
    */
   @Override
   public boolean _getNext()
   {
      try
      {
         next();
         return !_isOffEnd(); // YES if row is available
      }
      catch (ErrorConditionException ece)
      {
         return false;
      }
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lockType
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    */
   @Override
   public logical getNext(LockType lockType)
   {
      return wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(long lock)
   {
      LockType lockType = fromLockNowait(lock, 0);
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(NumberType lock)
   {
      LockType lockType = fromLockNowait(lock.longValue(), 0);
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(long lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait);
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(long lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait.longValue());
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(NumberType lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait);
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-NEXT method (KW_GET_NEXT).
    *
    * Moves a query object's result list pointer ahead one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the next record in the query is found. If the query is not
    *          open or the next record cannot be found (query is empty or the query result list
    *          pointer is on the last row), the method returns <code>false</code>. If the query
    *          object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getNext(NumberType lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
      return lockType == null ? logical.of(false) : wrapNext(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    */
   @Override
   public logical getPrevious()
   {
      try
      {
         previous();
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(long lock)
   {
      LockType lockType = fromLockNowait(lock, 0);
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lockType
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          if <code>lock</code> has not valid internal value.
    */
   @Override
   public logical getPrevious(LockType lockType)
   {
      return wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(NumberType lock)
   {
      LockType lockType = fromLockNowait(lock.longValue(), 0);
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(long lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait);
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(NumberType lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait);
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(long lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait.longValue());
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-PREV method (KW_GET_PREV).
    *
    * Moves a query object's result list pointer back one row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the previous record in the query is found. If the query is
    *          not open or the previous record cannot be found (query is empty or the query
    *          result list pointer is on the first row), the method returns <code>false</code>.
    *          If the query object handle is invalid, the method returns the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getPrevious(NumberType lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
      return lockType == null ? logical.of(false) : wrapPrevious(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    */
   @Override
   public logical getFirst()
   {
      try
      {
         first();
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          If <code>lock</code> has not valid internal value.
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(long lock)
   {
      LockType lockType = fromLockNowait(lock, 0);
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(NumberType lock)
   {
      LockType lockType = fromLockNowait(lock.longValue(), 0);
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lockType
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          if <code>lock</code> has not valid internal value.
    */
   @Override
   public logical getFirst(LockType lockType)
   {
      return wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          If <code>lock</code> or <code>nowait</code> have not valid internal values.
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(long lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait);
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(NumberType lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait);
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          If <code>lock</code> or <code>nowait</code> have not valid internal values.
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(long lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait.longValue());
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-FIRST method (KW_GET_1ST).
    *
    * Moves a query object's result list pointer to the first row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the first record in the query is found. If the query is not
    *          open or the first record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getFirst(NumberType lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
      return lockType == null ? logical.of(false) : wrapFirst(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    */
   @Override
   public logical getLast()
   {
      try
      {
         last();
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(long lock)
   {
      LockType lockType = fromLockNowait(lock, 0);
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(NumberType lock)
   {
      LockType lockType = fromLockNowait(lock.longValue(), 0);
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lockType
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  IllegalArgumentException
    *          if <code>lock</code> has not valid internal value.
    */
   @Override
   public logical getLast(LockType lockType)
   {
      return wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(long lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait);
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(NumberType lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait);
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(long lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait.longValue());
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-LAST method (KW_GET_LAST).
    *
    * Moves a query object's result list pointer to the last row.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if the last record in the query is found. If the query is not
    *          open or the last record cannot be found (query is empty), the method returns
    *          <code>false</code>. If the query object handle is invalid, the method returns
    *          the Unknown value (?).
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getLast(NumberType lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
      return lockType == null ? logical.of(false) : wrapLast(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    */
   @Override
   public logical getCurrent()
   {
      try
      {
         current();
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(long lock)
   {
      LockType lockType = fromLockNowait(lock, 0);
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(NumberType lock)
   {
      LockType lockType = fromLockNowait(lock.longValue(), 0);
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lockType of LockType
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    */
   @Override
   public logical getCurrent(LockType lockType)
   {
      return wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(long lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait);
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(NumberType lock, long nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait);
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(long lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock, nowait.longValue());
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Conversion of GET-CURRENT() method (KW_GET_CUR).
    *
    * Re-fetches the current record or records associated with the query.
    *
    * @param   lock
    *          SHARE-LOCK|EXCLUSIVE-LOCK|NO-LOCK
    * @param   nowait
    *          NO-WAIT
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    *
    * @throws  ErrorConditionException
    *          If the operation fails.
    */
   @Override
   public logical getCurrent(NumberType lock, NumberType nowait)
   {
      LockType lockType = fromLockNowait(lock.longValue(), nowait.longValue());
      return lockType == null ? logical.of(false) : wrapCurrent(lockType);
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * Default substitution arguments and a lock type of <code>NONE</code> are used for this test.
    * 
    * @return  <code>true</code> if the first record exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasAny()
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * The specified substitution arguments and a lock type of <code>NONE</code> are used for this test.
    * 
    * @param   values
    *          Query substitution parameters.
    * 
    * @return  <code>true</code> if the first record exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasAny(Object[] values)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * Default substitution arguments and the specified lock type are used for this test.
    * 
    * @param   lockType
    *          The lock type for the query.  This lock is not actually acquired, only tested for availability.
    * 
    * @return  <code>true</code> if the first record exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasAny(LockType lockType)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * The specified substitution arguments and lock type are used for this test.
    *
    * @param   values
    *          Query substitution parameters.
    * @param   lockType
    *          The lock type for the query.  This lock is not actually acquired, only tested for availability.
    * 
    * @return  <code>true</code> if the first record exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasAny(Object[] values, LockType lockType)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * Default substitution arguments and a lock type of <code>NONE</code> are used for this test.
    * 
    * @return  <code>true</code> if any record matching the query's criteria exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasOne()
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * The specified substitution arguments and a lock type of <code>NONE</code> are used for this test.
    * 
    * @param   values
    *          Query substitution parameters.
    * 
    * @return  <code>true</code> if any record matching the query's criteria exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasOne(Object[] values)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * Default substitution arguments and the specified lock type are used for this test.
    * 
    * @param   lockType
    *          The lock type for the query.  This lock is not actually acquired, only tested for availability.
    * 
    * @return  <code>true</code> if any record matching the query's criteria exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasOne(LockType lockType)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * The specified substitution arguments and lock type are used for this test.
    *
    * @param   values
    *          Query substitution parameters.
    * @param   lockType
    *          The lock type for the query.  This lock is not actually acquired, only tested for availability.
    * 
    * @return  <code>true</code> if any record matching the query's criteria exists, else <code>false</code>.
    * 
    * @throws  UnsupportedOperationException
    *          Always, this method must be implemented in derived classes.
    */
   public logical hasOne(Object[] values, LockType lockType)
   {
      throw new UnsupportedOperationException();
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentFirst(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentFirst(Object[], LockType)}</li>
    * </ul>
    *
    * @return  <code>true</code> if query could retrieve the first record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *          
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentFirst()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return first();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer. 
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentFirst(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentFirst(Object[], LockType)}</li>
    * </ul>
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    *
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *          
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentFirst(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return first(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentLast(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentLast(Object[], LockType)}</li>
    * </ul>
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentLast()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return last();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentLast(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentLast(Object[], LockType)}</li>
    * </ul>
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentLast(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return last(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentNext(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentNext(Object[], LockType)}</li>
    * </ul>
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   public boolean silentNext()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return next();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentNext(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentNext(Object[], LockType)}</li>
    * </ul>
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   public boolean silentNext(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return next(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentPrevious(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentPrevious(Object[], LockType)}</li>
    * </ul>
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   public boolean silentPrevious()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return previous();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentPrevious(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentPrevious(Object[], LockType)}</li>
    * </ul>
    * 
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   public boolean silentPrevious(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return previous(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer.  No more than one record may match
    * the criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentUnique(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentUnique(Object[], LockType)}</li>
    * </ul>
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentUnique()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return unique();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer. No more than one record may match
    * the criteria.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * Additional methods are defined for specific queries such as:
    * <ul>
    *    <li>{@link RandomAccessQuery#silentUnique(Object[])}</li>
    *    <li>{@link RandomAccessQuery#silentUnique(Object[], LockType)}</li>
    * </ul>
    * 
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   public boolean silentUnique(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return unique(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Reload the record most recently loaded into the backing record buffer,
    * if any.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * 
    * @return  <code>true</code> if query could retrieve the current record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record reload.
    */
   public boolean silentCurrent()
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return current();
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Reload the record most recently loaded into the backing record buffer,
    * if any.
    * <p>
    * This operation is executed in silent mode.
    * <p>
    * 
    * @param   lockType
    *          New lock type to apply to the record.
    * 
    * @return  <code>true</code> if query could retrieve the current record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record reload.
    */
   public boolean silentCurrent(LockType lockType)
   {
      ErrorHelper eh = getErrorHelper();
      boolean wasSilent = eh.isSilent();
      if (!wasSilent)
      {
         eh.setSilent(true);
      }
      
      boolean wasLenientOffEnd = isLenientOffEnd();
      if (!wasLenientOffEnd)
      {
         setLenientOffEnd(true);
      }
      
      try
      {
         return current(lockType);
      }
      catch (LegacyErrorException lex)
      {
         ErrorManager.processLegacyErrorException(lex, eh);
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            eh.setSilent(false);
         }
         
         if (!wasLenientOffEnd)
         {
            setLenientOffEnd(false);
         }
         
         eh.handleForwardPending();
      }
      
      return eh.getErrorStatus();
   }
   
   /**
    * Specifies the fields included in a record retrieval.
    *
    * @param dmo
    *        DMO proxy which defines the buffer for which the fields are specified.
    * @param fields
    *        The fields included in a record retrieval. If no fields are specified, only record
    *        identifiers are retrieved. No subscripts can be specified for extent fields.
    */
   public void include(DataModelObject dmo, String ... fields)
   {
      RecordBuffer buffer = ((BufferReference) dmo).buffer();
      if (buffer.isTemporary())
      {
         return;
      }
      
      if (included == null)
      {
         included = new IdentityHashMap<>();
      }
      
      collectFields(included, dmo, fields, buffer);
   }
   
   /**
    * Specifies the fields excluded from a record retrieval.
    *
    * @param dmo
    *        DMO proxy which defines the buffer for which the fields are specified.
    * @param fields
    *        The fields excluded from a record retrieval. If no fields are specified, complete
    *        records are retrieved. No subscripts can be specified for extent fields.
    */
   public void exclude(DataModelObject dmo, String ... fields)
   {
      RecordBuffer buffer = ((BufferReference) dmo).buffer();
      if (buffer.isTemporary())
      {
         return;
      }
      
      if (excluded == null)
      {
         excluded = new IdentityHashMap<>();
      }
      
      collectFields(excluded, dmo, fields, buffer);
   }
   
   /**
    * Conversion of NUM-BUFFERS() method (KW_NUM_BUFF).
    *
    * @return  the number of buffers in a query or DataSet object.
    */
   public integer numBuffers()
   {
      return new integer(getTableCount());
   }
   
   /**
    * Deletes the current row of a query's result list. Implements the 4GL
    * DELETE-RESULT-LIST-ENTRY() method.
    *
    * @return <code>true</code> on success.
    */
   public logical deleteResultListEntry()
   {
      return deleteResultListEntry(false);
   }
   
   /**
    * Deletes the current row of a query's result list. Implements the 4GL
    * DELETE-RESULT-LIST-ENTRY() method.
    *
    * @param  allowBetweenRows
    *         If <code>true</code> then the cursor can be positioned between rows (the next
    *         row will be deleted). <code>false</code> for conventional DELETE-RESULT-LIST-ENTRY()
    *         mode where the cursor should be positioned on a row (otherwise <code>false</code> is
    *         returned).
    *
    * @return always throws IllegalStateException.
    */
   public logical deleteResultListEntry(boolean allowBetweenRows)
   {
      throw new IllegalStateException(
            "DELETE-RESULT-LIST-ENTRY is not applicable to this type of query");
   }
   
   /**
    * Creates an entry in the result list for the current row. Implements the 4GL
    * CREATE-RESULT-LIST-ENTRY() method. Created entry contains id(s) of the record(s) currently
    * located in the backing buffer(s).
    *
    * @return <code>true</code> on success.
    */
   public logical createResultListEntry()
   {
      throw new IllegalStateException(
            "CREATE-RESULT-LIST-ENTRY is not applicable to this type of query");
   }
   
   /**
    * Setter for SKIP-DELETED-RECORD query attribute. 
    *
    * @param   on
    *          New value for the attribute.
    */
   @Override
   public void setSkipDeletedRecord(logical on)
   {
      if (on == null || on.isUnknown())
      {
         ErrorManager.recordOrShowError(4083, "SKIP-DELETED-RECORD", "QUERY widget");
         return; // attribute remains unchanged
      }
      
      boolean javaValue = on.booleanValue();
      if (browsed && javaValue)
      {
         ErrorManager.recordOrShowError(7363, "", "");
         // SKIP-DELETED-RECORD cannot be set on during BROWSE. (7363)
         
         ErrorManager.recordOrThrowError(3131, "SKIP-DELETED-RECORD", "", "QUERY widget");
         // Unable to set attribute SKIP-DELETED-RECORD in widget  of type QUERY widget (3131)
         return;
      }
      
      skipDeletedRecord = javaValue;
   }
   
   /**
    * Get value of SKIP-DELETED-RECORD attribute of the query.
    *
    * @return  the value of SKIP-DELETED-RECORD attribute of the query.
    */
   @Override
   public logical isSkipDeletedRecord()
   {
      return logical.of(skipDeletedRecord);
   }
   
   /**
    * Set the filter that handles parameters passed to the query. E.g. it can take input field
    * references and substitute them with snapshot values, if necessary.
    *
    * @param parameterFilter
    *        The filter that handles passed parameters.
    */
   @Override
   public void setParameterFilter(ParameterFilter parameterFilter)
   {
      this.parameterFilter = parameterFilter;
   }
   
   /**
    * Test whether the flag was set. 
    * 
    * @return  {@code true} during the returning of rows for populating a browse widget.
    * 
    * @see     #setFillingBrowseRows
    */
   @Override
   public boolean isFillingBrowseRows()
   {
      return fillingBrowseRows;
   }
   
   /**
    * Test whether the SKIP-DELETED-RECORD attribute is set.
    *
    * @return  the value of SKIP-DELETED-RECORD attribute of the query as a Java boolean.
    */
   boolean _isSkipDeletedRecord()
   {
      return skipDeletedRecord;
   }
   
   /**
    * Worker method for wrapping ErrorConditionException and reusing
    * in all P2JQuery.getNext() implementations
    *
    * @param   lockType
    *          instance of LockType
    *
    * @return  <code>true</code> if GET-NEXT() succeeds, else <code>false</code>.
    */
   private logical wrapNext(LockType lockType)
   {
      try
      {
         next(lockType);
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Worker method for wrapping ErrorConditionException and reusing
    * in all P2JQuery.getPrevious() implementations
    *
    * @param   lockType
    *          instance of LockType
    *
    * @return  <code>true</code> if GET-PREV() succeeds, else <code>false</code>.
    */
   private logical wrapPrevious(LockType lockType)
   {
      try
      {
         previous(lockType);
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Worker method for wrapping ErrorConditionException and reusing
    * in all P2JQuery.getFirst() implementations
    *
    * @param   lockType
    *          instance of LockType
    *
    * @return  <code>true</code> if GET-FIRST() succeeds, else <code>false</code>.
    */
   private logical wrapFirst(LockType lockType)
   {
      try
      {
         first(lockType);
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Worker method for wrapping ErrorConditionException and reusing
    * in all P2JQuery.getLast() implementations
    *
    * @param   lockType
    *          instance of LockType
    *
    * @return  <code>true</code> if GET-LAST() succeeds, else <code>false</code>.
    */
   private logical wrapLast(LockType lockType)
   {
      try
      {
         last(lockType);
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Worker method for wrapping ErrorConditionException and reusing
    * in all P2JQuery.getCurrent() implementations
    *
    * @param   lockType
    *          instance of LockType
    *
    * @return  <code>true</code> if GET-CURRENT() succeeds, else <code>false</code>.
    */
   private logical wrapCurrent(LockType lockType)
   {
      try
      {
         current(lockType);
         return logical.of(!_isOffEnd()); // YES if row is available
      }
      catch (ErrorConditionException exc)
      {
         return logical.of(false);
      }
   }
   
   /**
    * Populate the map of with the ORM properties specified in the given fields.
    * 
    * @param    target
    *           The target map.
    * @param    dmo
    *           The DMO instance.
    * @param    fields
    *           The fields to resolve.
    * @param    buffer
    *           The buffer instance.
    */
   private void collectFields(Map<DataModelObject, Property[]> target, 
                              DataModelObject                  dmo, 
                              String[]                         fields, 
                              RecordBuffer                     buffer)
   {
      DmoMeta meta = buffer.getDmoInfo();
      Property[] props = target.computeIfAbsent(dmo, (d) -> new Property[meta.getFieldSize()]);
      
      for (int i = 0; i < fields.length; i++)
      {
         Property field = meta.getFieldInfo(fields[i]);
         if (field == null)
         {
            // must be a custom extent
            List<Property> extFields = meta.getCustomExtentFieldInfo(fields[i]);
            if (extFields != null)
            {
               for (int j = 0; j < extFields.size(); j++)
               {
                  field = extFields.get(j);
                  props[field.propId - 1] = field;
               }
            }
            else
            {
               LOG.log(Level.WARNING, 
                       "Field " + fields[i] + " can not be resolved in buffer " + buffer.getLegacyName());
            }
         }
         else
         {
            props[field.propId - 1] = field;
         }
         // else: all the not-expanded fields will be fetched in bulk, unconditionally
      }
   }
   
   /** 
    * Get the recursive DATA-RELATION the query is to FILL for.  
    * @return the recursive DATA-RELATION the query is to FILL for
    */
   @Override
   public DataRelation getDataRelation() 
   {
       return this.relation;
   }
   
   /**
    * Set the recursive DATA-RELATION the query is to FILL for.
    * 
    * @param relation
    *        the relation in question   
    */
   @Override
   public void setDataRelation(DataRelation relation) 
   {
       this.relation = relation;
   }

   /**
    * Instances of this class are used as keys to cache translateSort output.
    */
   private static class SortCacheKey
   {
      /** Bound buffer alias */
      private final String boundAlias;

      /** Definition buffer alias */
      private final String defAlias;

      /** Bound buffer dmo interface */
      private final Class<?> boundDmoIface;

      /** Definition buffer dmo interface */
      private final Class<?> defDmoIface;

      /** The sort clause to be translated */
      private final String sort;

      /** The cached hash code.*/
      private final int cachedHash;

      /**
       * Constructor.
       *
       * @param   boundAlias
       *          Bound buffer alias
       * @param   defAlias
       *          Definition buffer alias
       * @param   boundDmoIface
       *          Bound buffer dmo interface
       * @param   defDmoIface
       *          Definition buffer dmo interface
       * @param   sort
       *          The sort clause to be translated.
       */
      SortCacheKey(String boundAlias,
                   String defAlias,
                   Class<?> boundDmoIface,
                   Class<?> defDmoIface,
                   String sort)
      {
         this.boundAlias = boundAlias;
         this.defAlias = defAlias;
         this.boundDmoIface = boundDmoIface;
         this.defDmoIface = defDmoIface;
         this.sort = sort;
         // since this object is immutable (all fields are private/final) we can compute now the
         // hash code and cache it for when needed, to improve performance
         this.cachedHash = _hashCode();
      }

      /**
       * Return a hash code consistent with {@link #equals}.
       *
       * @return  Hash code.
       */
      public int hashCode()
      {
         return cachedHash;
      }

      /**
       * Check this object for equivalence with another instance of this class.
       *
       * @param   o
       *          Another instance of this class.
       *
       * @return  <code>true</code> if instances are equivalent, else <code>false</code>.
       */
      public boolean equals(Object o)
      {
         if (!(o instanceof AbstractQuery.SortCacheKey) )
         {
            return false;
         }

         AbstractQuery.SortCacheKey that = (AbstractQuery.SortCacheKey) o;

         if (!boundAlias.equals(that.boundAlias)       ||
             !defAlias.equals(that.defAlias)           ||
             !boundDmoIface.equals(that.boundDmoIface) ||
             !defDmoIface.equals(that.defDmoIface)     ||
             !sort.equals(that.sort))
         {
            return false;
         }

         return true;
      }

      /**
       * Return a hash code consistent with {@link #equals}.
       *
       * @return  Hash code.
       */
      private int _hashCode()
      {
         int result = 17;
         result = 37 * result + sort.hashCode();
         result = 37 * result + boundAlias.hashCode();
         result = 37 * result + defAlias.hashCode();
         result = 37 * result + boundDmoIface.hashCode();
         result = 37 * result + defDmoIface.hashCode();
         return result;
      }
   }
}