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