FindQuery.java
/*
** Module : FindQuery.java
** Abstract : Backing query for converted FIND statements
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ECF 20060330 @25253 Created initial version. Backing query for
** converted find statements.
** 002 ECF 20060424 @25697 Added new constructor variants. Each existing
** variant now has a counterpart which does not
** expect a WhereExpression parameter.
** 003 ECF 20060424 @25698 Roll back previous change.
** 004 ECF 20060529 @26646 Override parent's initializeBuffer method.
** This implementation does nothing, to prevent
** buffer from being reset during query
** construction.
** 005 ECF 20061027 @30751 Replaced Object with DataModelObject for DMO
** arguments to methods/constructors. Required
** for compile-time type safety.
** 006 ECF 20061115 @31208 Override ancestor's implementation of
** isResetSnapshot() method. A converted FIND
** must clear the placeholder snapshot when a
** record is not found.
** 007 ECF 20070216 @32166 Explicitly set errorIfNull to true. This is
** required to override the default setting of
** false made by the superclass.
** 008 ECF 20070712 @34859 Moved all CAN-FIND related processing from
** parent class. Added support for client-side
** CAN-FIND processing within a where clause
** (including SHARE-LOCK support).
** 009 ECF 20071130 @36121 Removed isRefreshSnapshot() method. The
** RecordBuffer placeholder snapshot is no
** longer cleared by any query type when the
** query does not find a record to place in the
** buffer.
** 010 ECF 20071206 @36269 Modified udpateBuffer() method. Signature
** changed in superclass.
** 011 CA 20080312 @37431 Overloaded the registerChangeListener method
** to not register a change listener.
** 012 CA 20090731 @43466 Fixed the off-end behavior for FIND NEXT/PREV
** statements; on off-end, it must throw a QOEE
** exception and perform the defined END-KEY action
** (which defaults to UNDO, LEAVE if no explicit
** action is defined).
** 013 CA 20090806 @43534 Fixed regression related to H012 (@43466) - a
** FIND NEXT/PREV with a NO-ERROR clause must not
** end the block (a QOEE must not be thrown, so
** lenientOffEnd must be true if we are in silent
** error mode).
** 014 OM 20120612 Added bracketing for headless mode for execution of pl/java code in
** CAN-FIND function.
** 015 ECF 20131028 Import change.
** 016 ECF 20150715 Added isIdOnly method.
** 017 OM 20160715 Added support for template records.
** 018 GES 20160804 Switched from WhereExpression to lambda usage.
** 20160919 Changes to match parent class constructor/initialization changes.
** 019 ECF 20171220 Changes to error handling of CAN-FINDs.
** 020 ECF 20180201 Removed initializeBuffer.
** 021 ECF 20200906 New ORM implementation.
** 022 GES 20200731 Eliminated context-local lookups by using ErrorHelper.
** 023 OM 20200915 Handled query navigation on fatal error cases.
** 024 ECF 20210806 Corrected CAN-FIND return value in event of fatal error. Fixed nested query
** error detection/reporting.
** ECF 20210911 Javadoc fix.
** OM 20221103 New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache.
** 025 DDF 20230314 Add short-circuits for CAN-FIND queries.
** 026 DDF 20230316 Changed CAN-FIND short-circuits conditions.
** 027 DDF 20230904 Removed short-circuit because the table can have a record that was not flushed.
** 028 AL2 20230926 Short-circuit ANY find on temp database using fastHasRecords.
** 029 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 030 DDF 20240826 Added addExternalBuffers() method to return the current FindQuery instance
** for silent mode methods (FIND ... WHERE CAN-FIND() NO-ERROR).
** 031 DDF 20240902 The query initialization should be subjected to the silent mode and the
** exceptions thrown should be handled if necessary so the initialization
** will not be handled in the constructor, but will be delegated to the
** find operation.
** DDF 20240902 Override find methods, used to initialize the query.
** DDF 20240903 Override all remaining find methods, but only initialize the query once.
** DDF 20240903 Initialize the query for conversion emitted methods.
** 032 AL2 20241021 Allow hinting that this is a CAN-FIND query and the buffer is not going to be
** loaded by the found DMO.
** 033 OM 20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
** based on [sharedDb] parameter.
** Implementation of [setMultiTenantAlternative] method.
*/
/*
** 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.function.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
/**
* A convenience adapter for a <code>RandomAccessQuery</code>. Calls to
* <code>previous</code> and <code>next</code> will throw a
* <code>QueryOffEndException</code> when no data record is found, which is
* consistent with the Progress FIND NEXT/PREV statement behavior.
* <p>
* On query off-end, the FIND NEXT/PREV statement will execute the action
* defined for the END-KEY condition (which defaults to UNDO, LEAVE, but can
* be overridden by an user-defined action). This behavior is only valid to
* following blocks:
* <ul>
* <li>REPEAT</li>
* <li>PROCEDURE</li>
* <li>FOR</li>
* </ul>
* <p>
* This class also handles converted CAN-FIND statements.
*/
public final class FindQuery
extends RandomAccessQuery
{
/** Types of can-find lookups */
private static enum CanFind
{
/** Any record matching the query criteria */
ANY,
/** A unique record matching the query criteria */
ONE,
};
/** Mode of this query if used for CAN-FIND. */
private CanFind canFindMode = null;
/** Flag indicating whether buffer is updated by query. */
private boolean readOnly = false;
/** Data Model Object of the buffer in which the records will be retrived. */
private DataModelObject dmo;
/** Where clause, using HQL syntax, can be <code>null</code>. */
private String where;
/** Client-side where clause expression, can be <code>null</code>. */
private Supplier<logical> whereExpr;
/** Order by clause, using HQL syntax, can be <code>null<code>. */
private String sort;
/** DMO to which this query should join via a foreign relation, can be <code>null</code>. */
private DataModelObject inverse;
/** Substitution parameters for HQL queries. */
private Object[] args;
/** Lock type to apply to records retrieved. */
private LockType lockType;
/** If the query was already initialized. */
private boolean initialized = false;
/**
* Basic constructor which defaults record lock type to
* <code>LockType.SHARE</code>.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
*/
public FindQuery(DataModelObject dmo, String where, Supplier<logical> whereExpr, String sort)
{
this(dmo, where, whereExpr, sort, null, null, LockType.SHARE);
}
/**
* Constructor which is used when joining to another table using a foreign
* key. Defaults record lock type to <code>LockType.SHARE</code>.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param inverse
* DMO to which this query should join via a foreign relation.
* May be <code>null</code>.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
DataModelObject inverse)
{
this(dmo, where, whereExpr, sort, inverse, null, LockType.SHARE);
}
/**
* Constructor which defaults record lock type to
* <code>LockType.SHARE</code> and accepts default substitution parameters.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param args
* Substitution parameters for HQL queries. These will be used
* if not overridden by a record retrieval method.
*
* @throws IllegalArgumentException
* if any substitution argument provided is not of a supported
* type.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
Object[] args)
{
this(dmo, where, whereExpr, sort, null, args, LockType.SHARE);
}
/**
* Constructor which is used when joining to another table using a foreign
* key. Defaults record lock type to <code>LockType.SHARE</code> and
* accepts default substitution parameters.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param inverse
* DMO to which this query should join via a foreign relation.
* May be <code>null</code>.
* @param args
* Substitution parameters for HQL queries. These will be used
* if not overridden by a record retrieval method.
*
* @throws IllegalArgumentException
* if any substitution argument provided is not of a supported
* type.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
DataModelObject inverse,
Object[] args)
{
this(dmo, where, whereExpr, sort, inverse, args, LockType.SHARE);
}
/**
* Constructor which sets an explicit, default, record lock type.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param lockType
* Lock type to apply to records retrieved, if not overridden by
* a record retrieval method.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
LockType lockType)
{
this(dmo, where, whereExpr, sort, null, null, lockType);
}
/**
* Constructor which is used when joining to another table using a foreign
* key. Sets an explicit, default, record lock type.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param inverse
* DMO to which this query should join via a foreign relation.
* May be <code>null</code>.
* @param lockType
* Lock type to apply to records retrieved, if not overridden by
* a record retrieval method.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
DataModelObject inverse,
LockType lockType)
{
this(dmo, where, whereExpr, sort, inverse, null, lockType);
}
/**
* Constructor which sets an explicit, default, record lock type and
* accepts default substitution parameters.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param args
* Substitution parameters for HQL queries. These will be used
* if not overridden by a record retrieval method.
* @param lockType
* Lock type to apply to records retrieved, if not overridden by
* a record retrieval method.
*
* @throws IllegalArgumentException
* if any substitution argument provided is not of a supported
* type.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
Object[] args,
LockType lockType)
{
this(dmo, where, whereExpr, sort, null, args, lockType);
}
/**
* Constructor which is used when joining to another table using a foreign
* key. Sets an explicit, default, record lock type and accepts default
* substitution parameters.
*
* @param dmo
* Data model object which determines the record buffer into which
* records are retrieved.
* @param where
* Where clause, using HQL syntax. May be <code>null</code>.
* @param whereExpr
* Client-side where clause expression. May be <code>null</code>.
* @param sort
* Order by clause, using HQL syntax. May be <code>null</code>
* only if query is to be navigated only using one of the
* <code>unique</code> method variants.
* @param inverse
* DMO to which this query should join via a foreign relation.
* May be <code>null</code>.
* @param args
* Substitution parameters for HQL queries. These will be used
* if not overridden by a record retrieval method.
* @param lockType
* Lock type to apply to records retrieved, if not overridden by
* a record retrieval method.
*
* @throws IllegalArgumentException
* if any substitution argument provided is not of a supported
* type.
*/
public FindQuery(DataModelObject dmo,
String where,
Supplier<logical> whereExpr,
String sort,
DataModelObject inverse,
Object[] args,
LockType lockType)
{
this.dmo = dmo;
this.where = where;
this.whereExpr = whereExpr;
this.sort = sort;
this.inverse = inverse;
this.args = args;
this.lockType = lockType;
}
/**
* Set this query's can-find mode to 'any', meaning can it find any record
* which meets the query's criteria, regardless of duplicates.
*
* @return This instance, so that this method can be chained to the
* constructor and return this instance to the caller.
*/
public FindQuery setHasAny()
{
canFindMode = CanFind.ANY;
return this;
}
/**
* Set this query's can-find mode to 'one', meaning can it find one and
* only one record which meets the query's criteria.
*
* @return This instance, so that this method can be chained to the
* constructor and return this instance to the caller.
*/
public FindQuery setHasOne()
{
canFindMode = CanFind.ONE;
return this;
}
/**
* 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} are used for this test.
*
* @return {@code true} if any record matching the query's criteria exists, else {@code false}.
*/
public logical hasAny()
{
initializeQuery();
return hasAny(getCurrentArgs(), getLockType());
}
/**
* 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} are used for this test.
*
* @param values
* Query substitution parameters.
*
* @return {@code true} if any record matching the query's criteria exists, else {@code false}.
*/
public logical hasAny(Object[] values)
{
initializeQuery();
return hasAny(values, getLockType());
}
/**
* 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
* Lock type for the query. This lock is not actually acquired, only tested for availability.
*
* @return {@code true} if any record matching the query's criteria exists, else {@code false}.
*/
public logical hasAny(LockType lockType)
{
initializeQuery();
return hasAny(getCurrentArgs(), lockType);
}
/**
* 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
* Lock type for the query. This lock is not actually acquired, only tested for availability.
*
* @return {@code true} if any record matching the query's criteria exists, else {@code false}.
*/
public logical hasAny(Object[] values, LockType lockType)
{
initializeQuery();
if (!prepareFetch())
{
return (logical.of(false));
}
canFindMode = CanFind.ANY;
return (logical.of(canFindImpl(values, lockType)));
}
/**
* Indicate whether this query would produce a unique result.
* <p>
* Default substitution arguments and a lock type of {@code NONE} are used for this test.
*
* @return {@code true} if one and only one record matching the query's criteria exists, else
* {@code false}.
*/
public logical hasOne()
{
initializeQuery();
return hasOne(getCurrentArgs(), getLockType());
}
/**
* Indicate whether this query would produce a unique result.
* <p>
* The specified substitution arguments and a lock type of {@code NONE} are used for this test.
*
* @param values
* Query substitution parameters.
*
* @return {@code true} if one and only one record matching the query's criteria exists, else
* {@code false}.
*/
public logical hasOne(Object[] values)
{
initializeQuery();
return hasOne(values, getLockType());
}
/**
* 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
* Lock type for the query. This lock is not actually acquired, only tested for availability.
*
* @return {@code true} if one and only one record matching the query's criteria exists, else
* {@code false}.
*/
public logical hasOne(LockType lockType)
{
initializeQuery();
return hasOne(getCurrentArgs(), lockType);
}
/**
* 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
* Lock type for the query. This lock is not actually acquired, only tested for availability.
*
* @return {@code true} if one and only one record matching the query's criteria exists, else
* {@code false}.
*/
public logical hasOne(Object[] values, LockType lockType)
{
initializeQuery();
if (!prepareFetch())
{
return (logical.of(false));
}
canFindMode = CanFind.ONE;
return (logical.of(canFindImpl(values, lockType)));
}
/**
* Navigate to the first record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters and
* lock type.
*
* @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.
*/
@Override
public boolean first()
{
initializeQuery();
return super.first();
}
/**
* Navigate to the first record which meets the query criteria and retrieve
* it into its record buffer. Use the default lock type, but override the
* default substitution parameters with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
*
* @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.
*/
@Override
public boolean first(Object[] values)
{
initializeQuery();
return super.first(values);
}
/**
* Navigate to the first record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters, but
* override the default lock type with that provided.
*
* @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.
*/
@Override
public boolean first(LockType lockType)
{
initializeQuery();
return super.first(lockType);
}
/**
* Navigate to the first record which meets the query criteria and retrieve
* it into its record buffer. Override both the default substitution
* parameters, and the default lock type with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
* @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.
*/
@Override
public boolean first(Object[] values, LockType lockType)
{
initializeQuery();
return super.first(values, lockType);
}
/**
* Navigate to the last record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters and
* lock type.
*
* @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.
*/
@Override
public boolean last()
{
initializeQuery();
return super.last();
}
/**
* Navigate to the last record which meets the query criteria and retrieve
* it into its record buffer. Use the default lock type, but override the
* default substitution parameters with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
*
* @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.
*/
@Override
public boolean last(Object[] values)
{
initializeQuery();
return super.last(values);
}
/**
* Navigate to the last record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters, but
* override the default lock type with that provided.
*
* @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.
*/
@Override
public boolean last(LockType lockType)
{
initializeQuery();
return super.last(lockType);
}
/**
* Navigate to the last record which meets the query criteria and retrieve
* it into its record buffer. Override both the default substitution
* parameters, and the default lock type with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
* @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.
*/
@Override
public boolean last(Object[] values, LockType lockType)
{
initializeQuery();
return super.last(values, lockType);
}
/**
* Navigate to the next record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters and
* lock type.
* <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.
*
* @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.
*/
@Override
public boolean next()
{
initializeQuery();
return super.next();
}
/**
* Navigate to the next record which meets the query criteria and retrieve
* it into its record buffer. Use the default lock type, but override the
* default substitution parameters with those provided.
* <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.
*
* @param values
* Substitution values to use when issuing FQL queries.
*
* @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.
*/
@Override
public boolean next(Object[] values)
{
initializeQuery();
return super.next(values);
}
/**
* Navigate to the next record which meets the query criteria and retrieve
* it into its record buffer. Use the default substitution parameters, but
* override the default lock type with that provided.
* <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.
*
* @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.
*/
@Override
public boolean next(LockType lockType)
{
initializeQuery();
return super.next(lockType);
}
/**
* Navigate to the next record which meets the query criteria and retrieve
* it into its record buffer. Override both the default substitution
* parameters, and the default lock type with those provided.
* <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.
*
* @param values
* Substitution values to use when issuing FQL queries.
* @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 and <code>lenientOffEnd</code> is
* <code>false</code>.
*/
@Override
public boolean next(Object[] values, LockType lockType)
{
initializeQuery();
return super.next(values, lockType);
}
/**
* Navigate to the previous record which meets the query criteria and
* retrieve it into its record buffer. Use the default substitution
* parameters and lock type.
* <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.
*
* @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.
*/
@Override
public boolean previous()
{
initializeQuery();
return super.previous();
}
/**
* Navigate to the previous record which meets the query criteria and
* retrieve it into its record buffer. Use the default lock type, but
* override the default substitution parameters with those provided.
* <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.
*
* @param values
* Substitution values to use when issuing FQL queries.
*
* @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.
*/
@Override
public boolean previous(Object[] values)
{
initializeQuery();
return super.previous(values);
}
/**
* Navigate to the previous record which meets the query criteria and
* retrieve it into its record buffer. Use the default substitution
* parameters, but override the default lock type with that provided.
* <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.
*
* @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.
*/
@Override
public boolean previous(LockType lockType)
{
initializeQuery();
return super.previous(lockType);
}
/**
* Navigate to the previous record which meets the query criteria and
* retrieve it into its record buffer. Override both the default
* substitution parameters, and the default lock type with those provided.
* <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.
*
* @param values
* Substitution values to use when issuing FQL queries.
* @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 and <code>lenientOffEnd</code> is
* <code>false</code>.
*/
@Override
public boolean previous(Object[] values, LockType lockType)
{
initializeQuery();
return super.previous(values, lockType);
}
/**
* Reload the record most recently loaded into the backing record buffer,
* if any. Use the default lock type.
*
* @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.
*/
@Override
public boolean current()
{
initializeQuery();
return super.current();
}
/**
* Reload the record most recently loaded into the backing record buffer,
* if any. Override the default lock type.
*
* @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, or the lock could
* not be transitioned to the requested type.
*/
@Override
public boolean current(LockType lockType)
{
initializeQuery();
return super.current(lockType);
}
/**
* 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. Use the default substitution parameters and lock type.
*
* @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.
*/
@Override
public boolean unique()
{
initializeQuery();
return super.unique();
}
/**
* 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. Use the default lock type, but override the default
* substitution parameters with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
*
* @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.
*/
@Override
public boolean unique(Object[] values)
{
initializeQuery();
return super.unique(values);
}
/**
* 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. Use the default substitution parameters, but override
* the default lock type with that provided.
*
* @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.
*/
@Override
public boolean unique(LockType lockType)
{
initializeQuery();
return super.unique(lockType);
}
/**
* 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. Override both the default substitution parameters, and
* the default lock type with those provided.
*
* @param values
* Substitution values to use when issuing FQL queries.
* @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.
*/
@Override
public boolean unique(Object[] values, LockType lockType)
{
initializeQuery();
return super.unique(values, lockType);
}
/**
* Indicate whether this query is a simple RECID/ROWID lookup FIND query. This has implications as to
* whether a 7254 error will be raised in the event this FIND is nested within a function called within
* another query's WHERE clause.
*
* @return {@code true} if this is a RECID/ROWID lookup FIND query, else {@code false}.
*/
public boolean isFindByRowid()
{
return getHelper().getFQLPreprocessor().isFindByRowid();
}
/**
* 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.
*/
@Override
public FindQuery addExternalBuffers(DataModelObject... dmos)
{
initializeQuery();
super.addExternalBuffers(dmos);
return this;
}
/**
* Initializes the query at the latest moment possible (right before executing a find operation).
* This method is compatible with silent mode operations, meaning that the exceptions
* {@link LegacyErrorException} and {@link ErrorConditionException} that are thrown when the
* query is initialized are caught and handled.
* <p>
* Ensures the query is only initialized once.
*/
public void initializeQuery()
{
if (initialized)
{
return;
}
super.initialize(dmo, where, whereExpr, sort, null, inverse, args, lockType);
setErrorIfNull(true);
initialized = true;
}
/**
* Indicate whether a nested query error should be suppressed (i.e., reported only as a warning, but not
* interrupt control flow), when detected while preprocessing this query's substitution parameters.
*
* @return {@code false}.
*/
@Override
protected boolean suppressNestedQueryError()
{
return false;
}
/**
* This class doesn't need to register a change listener, so this method does nothing.
*/
protected void registerChangeListener()
{
// do nothing
}
/**
* Override the parent's method to return <code>true</code> if this is a CAN-FIND, else
* delegate to the parent's implementation.
*
* @return <code>true</code> to produce a projection query, else <code>false</code>.
*/
@Override
protected boolean isIdOnly()
{
if (canFindMode != null)
{
return true;
}
return super.isIdOnly();
}
/**
* Prepare buffer associated with this query for the query's execution.
* If this query is in read-only mode, do nothing. Otherwise, invoke the
* superclass' implementation.
*
* @throws ValidationException
* if the parent's implementation is invoked and a buffer flush
* triggers a validation error for the record currently stored in
* the buffer.
*/
protected void prepareBuffer()
throws ValidationException
{
if (!readOnly)
{
super.prepareBuffer();
}
}
/**
* Update the record buffer with the given DMO, applying the specified
* lock type and error-if-null setting. This implementation causes the
* record buffer's internal placeholder snapshot to be reset if
* <code>dmo</code> is null.
*
* @param dmo
* Record to be stored in buffer.
* @param lockType
* Lock type associated with the record, which will be remembered
* by the buffer.
* @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.
* @param offEnd
* Enum indicating whether query is off-end, and if so, in which
* direction.
*/
protected void updateBuffer(Record dmo, LockType lockType, boolean errorIfNull, OffEnd offEnd)
{
if (!readOnly)
{
super.updateBuffer(dmo, lockType, errorIfNull, offEnd);
}
}
/**
* Override the superclass' implementation to throw an exception
* unconditionally when in read-only mode. This ensures that any locking
* error which occurs during a converted CAN-FIND is not reported to the
* user, but does affect the outcome of the CAN-FIND test.
* <p>
* When the query is not in read-only mode, handling is delegated to the
* superclass' implementation.
*
* @param exc
* Cause exception.
*/
protected void handleExecuteException(LockUnavailableException exc)
{
if (readOnly)
{
ErrorManager.throwError(445,
getBuffer().getLegacyName() + " record is locked",
exc,
true);
}
else
{
super.handleExecuteException(exc);
}
}
/**
* Override the superclass' implementation to throw an exception
* unconditionally when in read-only mode. This ensures that any
* validation error which occurs during a converted CAN-FIND is not
* reported to the user, but does affect the outcome of the CAN-FIND test.
* <p>
* When the query is not in read-only mode, handling is delegated to the
* superclass' implementation.
*
* @param exc
* Cause exception.
*/
protected void handleExecuteException(ValidationException exc)
{
if (readOnly)
{
ErrorManager.throwError(exc, true);
}
else
{
super.handleExecuteException(exc);
}
}
/**
* Override the superclass' implementation to throw an exception
* unconditionally when in read-only mode. This ensures that any
* validation error which occurs during a converted CAN-FIND is not
* reported to the user, but does affect the outcome of the CAN-FIND test.
* <p>
* When the query is not in read-only mode, handling is delegated to the
* superclass' implementation.
*
* @param exc
* Cause exception.
*/
protected void handleExecuteException(PersistenceException exc)
{
if (readOnly)
{
ErrorManager.throwError(exc, true);
}
else
{
super.handleExecuteException(exc);
}
}
/**
* Test whether the {@code first} / {@code last} navigation will throw {@code QueryOffEndException} if the
* initialization failed. The super class must throw the exception if it fails the initialization in order
* to prematurely exit the loop on basic navigation. Now we are processing a simple {@code FIND} statement
* which does not require such thing because there is no loop to break.
*
* @return always {@code false}.
*/
protected boolean throwExceptionOnFatalError()
{
return false;
}
/**
* Checks if the buffer associated with this query will be loaded as the result of this query execution.
* This should always hold, except for CAN-FIND queries that do not modify the state of the buffer.
*
* @return {@code true} if the buffer is going to be loaded by the query.
*/
@Override
protected boolean isBufferLoadedByQuery()
{
return canFindMode == null;
}
/**
* Indicate whether a record can be found with meets this query's criteria. The success of this
* search is governed by the can-find mode of this query:
* <ul>
* <li>ANY: return {@code true} if <i>any</i> record can be found which meets the query's
* criteria, regardless of duplicates. If no such record is found, return
* {@code false}.</li>
* <li>ONE: return {@code true} if <i>one and only one</i> record can be found which meets
* the query's criteria. If no such record is found, or if more than one such
* record is found, return {@code false}.</li>
* </ul>
*
* @param values
* Query substitution parameters
* @param lockType
* Lock type to use when performing test. Lock is not actually acquired, only tested.
*
* @return See above.
*/
private boolean canFindImpl(Object[] values, LockType lockType)
{
if (lockType == null)
{
lockType = LockType.NONE;
}
RecordBuffer buffer = this.getBuffer();
FQLHelper helper = getHelper();
if (helper.getFQLPreprocessor().isFindByRowid() &&
helper.getFQLPreprocessor().getFindByRowid(values) < 0)
{
// permanent template records ids are validated against _file meta table. If requested
// rowid is not the proper one, then the CAN-FIND returns false
// temporary tables don't have template records or at least they are not findable using
// _file meta table. Returning false for negative rowid lookup.
return !buffer.isTemporary() &&
helper.isFindTemplate(values, buffer.getDMOImplementationClass());
}
// otherwise the query is not accessing records by their rowid or the value is positive so
// it must be checked against the database
// slight optimization to short-circuit cases where we can answer an ANY query with a
// direct-access. this is only available for temporary database.
if (canFindMode == CanFind.ANY && buffer.isTemporary() && !hasConstraints())
{
try
{
// make sure we make the records visible before attempting a direct-access call
honorRecordNursery(null);
Boolean hasRecords = buffer.fastHasRecords();
// this should always suceed for temporary database.
if (hasRecords != null)
{
return hasRecords;
}
}
catch (PersistenceException e)
{
ErrorManager.recordOrThrowError(e);
return false;
}
}
if (canFindMode == CanFind.ONE && helper.getFQLPreprocessor().isUniqueFind(true))
{
canFindMode = CanFind.ANY;
}
boolean unique = false;
switch (canFindMode)
{
case ANY:
activateFirst();
break;
case ONE:
activateUnique();
unique = true;
break;
}
Record dmo = null;
readOnly = true;
boolean result = false;
try
{
// temporarily enter headless mode when executing CAN-FIND
buffer.txHelper.errHlp.setCanFindBracket(true); // enter CAN-FIND: push a CAN-FIND level
dmo = execute(values, lockType, unique, false);
result = (dmo != null && testLock(dmo, lockType));
}
catch (ErrorConditionException exc)
{
result = false;
}
finally
{
// restore previous headless mode
buffer.txHelper.errHlp.setCanFindBracket(false); // leave CAN-FIND: pop out a level
readOnly = false;
}
return result;
}
/**
* Test whether the specified lock type could be acquired for the given
* DMO, but do not actually acquire the lock.
* <p>
* Note: because this method does not actually acquire the lock, it is
* not a reliable test, since another context could easily acquire the
* target lock after this method returns <code>true</code>, but before any
* further action is taken on that information in the current context.
*
* @param dmo
* DMO for which the lock test should occur.
* @param lockType
* Lock type to test.
*
* @return <code>true</code> if the lock could be acquired at the moment
* of the test, else <code>false</code>.
*/
private boolean testLock(Record dmo, LockType lockType)
{
if (lockType != LockType.NONE)
{
RecordBuffer buffer = getBuffer();
Persistence persistence = buffer.getPersistence();
String table = buffer.getTable();
Long id = dmo.primaryKey();
try
{
RecordIdentifier<String> ident = new RecordIdentifier<>(table, id);
persistence.lock(lockType, ident, false, !buffer.getDmoInfo().multiTenant);
}
catch (LockUnavailableException exc)
{
return false;
}
}
return true;
}
}