AdaptiveFind.java
/*
** Module : AdaptiveFind.java
** Abstract : Query which adapts its retrieval type from dynamic to preselect
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- --------------------------------Description-------------------------------------
** 001 ECF 20060801 @28343 Created initial version. Query which adapts its
** retrieval type from dynamic to preselect, and
** which starts its search from the current buffer
** record(s).
** 002 ECF 20060803 @28386 Fixed multiple flaws. Set lenientOffEnd to true
** in constructor and use a FindQuery as the dynamic
** replacement query delegate. Improved result set
** invalidation strategy.
** 003 ECF 20060901 @29153 Fixed NPE. Cannot safely use invalidBuffer to
** extract DMO proxy in createSimpleQuery().
** 004 ECF 20060925 @29961 Check for a pending error before retrieving a
** record. Retrieval is not attempted if an error is
** pending in the ErrorManager.
** 005 ECF 20061003 @30151 Call prepareFetch() instead of obsolete method
** checkPendingError(). Implemented releaseBuffers()
** to override do-nothing AbstractQuery
** implementation.
** 006 ECF 20061020 @30564 Simplified constructors. Removed constructors
** which contained client-side where expression,
** inverse DMO, or query substitution parameters
** arguments, since none of these are supported by
** the adaptive find model.
** 007 ECF 20061027 @30747 Replaced Object with DataModelObject for DMO
** arguments to methods/constructors. Required for
** compile-time type safety.
** 008 ECF 20061115 @31207 Override ancestor's implementation of
** isResetSnapshot() method. A converted FIND must
** clear the placeholder snapshot when a record is
** not found.
** 009 ECF 20070225 @32233 Added isGlobalCleanup() method. Used by
** PreselectQuery to determine that this object
** registers its results resources cleaner to the
** global (i.e., external proc) scope.
** 010 ECF 20070308 @32333 Fixed behavior for first(). Upon failure to find
** a record, if not in looping mode, this method
** must report a 'FIND FIRST/LAST failed...'
** message. Note that this covers the FIND
** LAST/PREVIOUS case, because AdaptiveFind inverts
** the sort direction for these queries and always
** invokes first().
** 011 ECF 20070412 @32973 Fixed createSimpleQuery(). Always use lock type
** of NONE for the dynamic query. This will be
** overridden by PreselectQuery.fetch(), if a
** stricter lock is required.
** 012 ECF 20070705 @34399 Fixed maybeInvalidate(). Invalidation is required
** if record buffer releases a record AdaptiveFind
** has loaded.
** 013 ECF 20071130 @36117 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.
** 014 SVL 20080418 @38042 Added new convenience constructor.
** 015 ECF 20080813 @39552 Support invalidation of results based on DMO
** change events in other sessions. Currently, this
** support is limited to changes which affect an
** index on the backing table.
** 016 ECF 20080826 @39572 Fixed isGlobalValidation(). Can only return true
** for non-temp tables.
** 017 ECF 20081031 @40304 Modified methods due to changes in parent class.
** 018 ECF 20090603 @42582 Made maybeInvalidate() invalidation approach more
** conservative. We now invalidate if there is any
** uncommitted change to the backing table, in any
** session.
** 019 ECF 20090716 @43211 Implemented lazy initialization. Backing buffer
** cannot be initialized and flushed until the first
** attempt to find a record, once we are sure the
** buffer's scope has been opened.
** 020 CA 20090731 @43463 Removed lenientOffEnd activation in c'tor. The
** query is allowed to throw QueryOffEndException's
** (to be consistent with FIND NEXT/PREV behavior).
** 021 CA 20090806 @43535 Fixed regression related to H020 (@43463) - 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).
** 022 ECF 20131028 Import change.
** 023 SVL 20140604 Resolved ambiguity with the newly added constructor of the
** superclass.
** 024 ECF 20140814 Replaced RecordBuffer.flushAll with flush in initialize method.
** 025 ECF 20150701 Changed addComponent to accommodate query component refactoring.
** 026 IAS 20160701 Fixed legacy name in the [565] error message.
** 027 ECF 20160728 Added optional, external buffers to constructors.
** 028 GES 20160919 Changes to match parent class constructor/initialization changes.
** 029 GES 20170831 Shifting to a deferred initialize() approach that is the same
** as the parent class.
** 030 ECF 20170913 Ensure initialization only occurs once.
** 031 GES 20171220 Changes to match new silent mode implementation.
** 032 OM 20171129 releaseBuffers() is overwritten from P2JQuery.
** 033 ECF 20190302 Renamed setExternalBuffers to addExternalBuffers.
** 034 ECF 20190813 Change to RecordBuffer.release API.
** 035 OM 20200822 Fixed query invalidation.
** 036 GES 20200731 Eliminated context-local lookups by using ErrorHelper.
** CA 20210310 A dynamic predicate must set the default lock to NONE, instead of SHARE.
** SVL 20230109 Reflected PreselectQuery.components() now providing direct access to the
** components array in order to improve performance.
** 037 SVL 20230516 Support for more granular configuration of dirty sharing functionality.
** 038 DDF 20230627 Replaced static variables of DirtyShareSupport with static method calls.
** 039 ES 20231203 Changed first, next methods to return boolean,instead of void. These methods now
** returns true if operation was successful, and false not and lenientOffEnd is set
** on true, otherwise it thrown an QQE exception.
** 040 AL2 20240321 Suppressed earlyInvalidte from AdaptiveQuery.
** 041 CA 20240402 Refactored so the query navigation is delegated to a wrapped query, which can be
** re-built if the buffers used at the AdaptiveFind's creation have been switched.
** 042 AL2 20240424 Match the new signature for earlyInvalidte in AdaptiveQuery.
** 043 CA 20240802 The query wrapper must register for finalizable support in the block where it gets
** initialized.
** 044 DDF 20240814 Added silent method equivalents to first, next to avoid using silent mode
** lambdas.
** DDF 20240821 lenientOffEnd flag should be set for each silent mode method because the silent
** flag retrieved through the ErrorHelper is set after initialization.
** DDF 20240822 Delegate lenientOffEnd to the silent mode methods in AdaptiveFindQuery.
** Made ErrorHelper instance a class member for performance improvements.
** DDF 20240828 Removed silent mode methods that are accessible through AbstractQuery and
** moved the errorHelper property to AbstractQuery.
** 045 TJD 20240412 Renamed isEnabledCrossSection to isEnabledCrossSession
** TJD 20240912 DirtyShareSupport flags logic dependency update
*/
/*
** 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 com.goldencode.p2j.persist.dirty.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.ErrorManager.ErrorHelper;
/**
* This instance is a wrapper to an implementation of {@link AdaptiveQuery} which may begin in dynamic record
* retrieval mode. If so, it switches to preselect mode as soon as possible. It is intended as the backing
* implementation for converted FIND statements which can be executed in succession without requiring a new
* query to be executed for each record retrieved. Candidates are combinations of FIND FIRST/NEXT and
* FIND LAST/PREV, or instances of FIND NEXT and FIND PREV which operate in loops.
* <p>
* This type of query by its nature operates only on a single table and therefore on a single record buffer.
* If the record buffer already is in use, its latest record is used as a reference point for the next record
* to be retrieved and the query is initialized in dynamic mode. Otherwise, the query is initialized in
* preselect mode.
* <p>
* The wrapped {@link #query} will always be {@link #createQuery() re-created} if the query's buffers (saved
* at the {@link AdaptiveFind}'s instantiation) have been bound to other buffers (i.e. the referenced buffer
* has switched).
*/
public class AdaptiveFind
{
/** The real wrapped query. */
private AdaptiveFindQuery query = null;
/**
* The list of query initialization actions which will be applied to {@link #query}, when it will be
* {@link #createQuery() created}.
*/
private List<Consumer<AdaptiveFindQuery>> init = new LinkedList<>();
/** Flag indicating whether this object has been initialized */
private boolean initialized = false;
/** The original DMO reference used to create this instance. */
private DataModelObject dmo = null;
/** The original external DMOs references used for initialization. */
private DataModelObject[] externalDMOs = null;
/**
* Default constructor. Initialization is deferred until <code>initialize()</code> is called.
*/
public AdaptiveFind()
{
super();
}
/**
* Convenience deferred initializer designed for a single-table adaptive query. The where clause must
* expect no substitution parameters. A share lock will be applied to retrieved records.
*
* @param dmo
* DMO proxy which defines buffer and record type.
* @param where
* FQL where clause. May be <code>null</code>.
* @param sort
* FQL order by clause.
* @param dmos
* Zero or more external buffers.
*
* @return The current instance (<code>this</code>) so that chaining can be used.
*/
public AdaptiveFind initialize(DataModelObject dmo,
String where,
String sort,
DataModelObject... dmos)
{
return initialize(dmo, where, sort, LockType.SHARE, dmos);
}
/**
* Convenience deferred initializer designed for a single-table adaptive query. The where clause must
* expect no substitution parameters.
*
* @param dmo
* DMO proxy which defines buffer and record type.
* @param where
* FQL where clause. May be <code>null</code>.
* @param sort
* FQL order by clause.
* @param lockType
* Lock type to apply to records retrieved by this query.
* @param dmos
* Zero or more external buffers.
*
* @return The current instance (<code>this</code>) so that chaining can be used.
*/
public AdaptiveFind initialize(DataModelObject dmo,
String where,
String sort,
LockType lockType,
DataModelObject... dmos)
{
if (initialized)
{
return this;
}
// save the 'ReferenceProxy' for all DMOs
this.dmo = dmo;
this.externalDMOs = dmos;
init.add((query) -> { query.initialize(dmo, where, sort, lockType, dmos); });
initialized = true;
TransactionManager.registerFinalizable(new Finalizable()
{
@Override
public void retry() { }
@Override
public void iterate() { }
@Override
public void finished() { }
@Override
public void deleted()
{
query = null;
initialized = false;
init.clear();
AdaptiveFind.this.dmo = null;
externalDMOs = null;
}
}, false);
return this;
}
/**
* Convenience deferred initializer designed for a single-table adaptive query. The where clause must
* expect no substitution parameters. A share lock will be applied to retrieved records.
*
* @param dmo
* DMO proxy which defines buffer and record type.
* @param where
* FQL where clause. May be <code>null</code>.
* @param sort
* FQL order by clause.
* @param inverseSorting
* Whether this query has the inverse sorting direction.
* @param dmos
* Zero or more external buffers.
*
* @return The current instance (<code>this</code>) so that chaining can be used.
*/
public AdaptiveFind initialize(DataModelObject dmo,
String where,
String sort,
boolean inverseSorting,
DataModelObject... dmos)
{
init.add((query) ->
{
query.inverseSorting = inverseSorting;
});
return initialize(dmo, where, sort, LockType.SHARE, dmos);
}
/**
* Retrieve the next composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
*
* @return <code>true</code> if query could retrive the next record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean next()
{
return next(null);
}
/**
* Retrieve the next composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default lock type set for each query
* component).
*
* @return <code>true</code> if query could retrive the next record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean next(LockType lockType)
{
createQuery();
return query.next(lockType);
}
/**
* Retrieve the next composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
* <p>
* This operation is executed in silent mode.
*
* @return <code>true</code> if query could retrive the next record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean silentNext()
{
return silentNext(null);
}
/**
* Retrieve the next composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
* <p>
* This operation is executed in silent mode.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default lock type set for each query
* component).
*
* @return <code>true</code> if query could retrive the next record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean silentNext(LockType lockType)
{
createQuery();
return query.silentNext(lockType);
}
/**
* Retrieve the first composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
*
* @return <code>true</code> if query could retrive the first record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean first()
{
return first(null);
}
/**
* Retrieve the first composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default lock type set for each query
* component).
*
* @return <code>true</code> if query could retrive the first record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean first(LockType lockType)
{
createQuery();
return query.first(lockType);
}
/**
* Retrieve the first composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
* <p>
* This operation is executed in silent mode.
*
* @return <code>true</code> if query could retrive the first record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean silentFirst()
{
return silentFirst(null);
}
/**
* Retrieve the first composite row of results for the query. The underlying buffer is updated with the
* appropriate record.
* <p>
* This operation is executed in silent mode.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default lock type set for each query
* component).
*
* @return <code>true</code> if query could retrive the first record successfully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records are available and the query is
* set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean silentFirst(LockType lockType)
{
createQuery();
return query.silentFirst(lockType);
}
/**
* Create the {@link #query} - this will apply all recorded {@link #init initialization steps}.
*/
private void createQuery()
{
checkQuery();
if (query == null)
{
query = new AdaptiveFindQuery();
for (Consumer<AdaptiveFindQuery> part : init)
{
part.accept(query);
}
}
}
/**
* Check the query if the referenced buffers have changed their bound buffers. If so, close it, so
* {@link #createQuery()} will be initialized.
*/
private void checkQuery()
{
if (query == null)
{
return;
}
boolean buffersChanged = false;
// compare the query main buffer with the current buffer
if (query.buffer != ((BufferReference) dmo).buffer())
{
buffersChanged = true;
}
else if (externalDMOs != null && externalDMOs.length > 0)
{
for (int i = 0; i < externalDMOs.length; i++)
{
if (query.externalBuffers[i] != ((BufferReference) externalDMOs[i]).buffer())
{
buffersChanged = true;
break;
}
}
}
if (buffersChanged)
{
// close the existing query - it will be re-created
query.close();
query = null;
}
}
/**
* The wrapped query to which the query navigation calls are delegated.
*/
private class AdaptiveFindQuery
extends AdaptiveQuery
{
/** Buffer updated by this query */
private RecordBuffer buffer = null;
/** Record most recently found by this query */
private Record lastFound = null;
/** The resolved external buffers when the query was initialized. */
private RecordBuffer[] externalBuffers = null;
/**
* Convenience deferred initializer designed for a single-table adaptive query. The where clause must
* expect no substitution parameters.
*
* @param dmo
* DMO proxy which defines buffer and record type.
* @param where
* FQL where clause. May be <code>null</code>.
* @param sort
* FQL order by clause.
* @param lockType
* Lock type to apply to records retrieved by this query.
* @param dmos
* Zero or more external buffers.
*/
public void initialize(DataModelObject dmo,
String where,
String sort,
LockType lockType,
DataModelObject... dmos)
{
initialize(dmo, where, null, sort, (DataModelObject) null, null, lockType);
buffer = ((BufferReference) dmo).buffer();
// if the NO-ERROR clause is specified, then a QOEE must not be thrown
setLenientOffEnd(buffer.txHelper.errHlp.isSilent());
// set external buffers, if any
if (dmos != null && dmos.length > 0)
{
addExternalBuffers(dmos);
externalBuffers = new RecordBuffer[dmos.length];
for (int i = 0; i < dmos.length; i++)
{
externalBuffers[i] = ((BufferReference) dmos[i]).buffer();
}
}
// start off in dynamic mode if the buffer already is active
if (buffer.isActive() && buffer.getSnapshot() != null)
{
AdaptiveComponent comp = (AdaptiveComponent) components().get(0);
invalidate(comp);
}
}
/**
* Retrieve the first composite row of results for the query. The
* underlying buffer is updated with the appropriate record.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default
* lock type set for each query component).
*
* @return <code>true</code> if query could retrive the first record succefully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records
* are available and the query is set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail
* in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean first(LockType lockType)
{
init();
if (!prepareFetch())
{
return false;
}
maybeInvalidate();
boolean available = false;
try
{
available = super.first(lockType);
}
catch (QueryOffEndException exc)
{
ErrorManager.recordOrThrowError(
new PersistenceException(
"FIND FIRST/LAST failed for table " + buffer.getLegacyName(),
565));
}
if (available)
{
lastFound = buffer.getSnapshot();
}
return available;
}
/**
* Retrieve the next composite row of results for the query. The
* underlying buffer is updated with the appropriate record.
*
* @param lockType
* Lock type to apply to records retrieved (overrides default
* lock type set for each query component).
*
* @return <code>true</code> if query could retrive the next record succefully;
* <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
*
* @throws ErrorConditionException
* if the query or the fetch of any record fails, or if no records
* are available and the query is set to fail in this case.
* @throws QueryOffEndException
* if no records are available and the query is set not to fail
* in this case.
* @throws IllegalStateException
* if no query component has been added to this query.
*/
public boolean next(LockType lockType)
{
init();
if (!prepareFetch())
{
return false;
}
maybeInvalidate();
boolean available = super.next(lockType);
if (available)
{
lastFound = buffer.getSnapshot();
}
return available;
}
/**
* If in silent error mode, this method will always return <code>true</code>.
* Else, it will let the {@link PreselectQuery#isLenientOffEnd()} decide
* the mode.
*
* @return <code>true</code> if an exception will be thrown, else
* <code>false</code>.
*/
public boolean isLenientOffEnd()
{
return buffer.txHelper.errHlp.isSilent() || super.isLenientOffEnd();
}
/**
* Report whether this query must check for changes across all sessions and
* possibly invalidate its current results if any are found. If not, this
* query will only honor local changes to invalidate results.
*
* @return <code>true</code> if query is against a permanent table;
* <code>false</code> if query is against a temporary table.
*/
protected boolean isGlobalInvalidation()
{
return !buffer.isTemporary();
}
/**
* Add a component to the query. This implementation overrides the parent
* method only to disallow multiple tables. Because it is called from a
* superclass constructor, instance variables must not be initialized in
* this method.
*
* @param comp
* Query component to be added.
*
* @throws UnsupportedOperationException
* if invoked more than once per instance; also:
* if attempting an outer join (temporary);
* if iteration type is not <code>NEXT</code> (temporary);
*/
protected void addComponent(QueryComponent comp)
{
if (getTableCount() > 0)
{
throw new UnsupportedOperationException(
"Multi-table adaptive finds are not supported");
}
super.addComponent(comp);
}
/**
* Release the current record, if any, from the buffer which is used by this query.
*
* @throws QueryOffEndException
* if the buffer contained a record and it was released.
*/
public void releaseBuffers()
{
// although this is implemented as an iterating query, it maps back to a FIND statement,
// so we allow a write trigger to fire
buffer.release(true);
}
/**
* Indicate whether results resources should be cleaned up upon exiting the current program's top-level
* scope or upon exiting the nearest enclosing scope in which the results were created.
*
* @return <code>true</code>.
*/
protected boolean isTopLevelCleanup()
{
return true;
}
/**
* Check if this query should operate in dynamic mode right from the start.
*
* This kind of query is invalidated by cross-session dirty context anyway, no need for extra handling.
*
* @return Always return {@code false} as this isn't ever going to be invalidated early.
*/
@Override
protected boolean earlyInvalidate()
{
return false;
}
/**
* Initialize and flush the buffer.
*/
private void init()
{
buffer.initialize();
try
{
buffer.flush();
}
catch (ValidationException exc)
{
// a validation error can only result from flushing an unrelated record currently in
// the buffer in preparation for a query result; since this will not be within the
// context of any silent error mode under which this query is running, we instruct
// the error manager to not suppress the message
ErrorManager.throwError(exc, false);
}
}
/**
* Invalidate the current result set associated with this query if the record buffer's contents have
* changed since the last time this query updated the buffer. If in preselect mode, this will switch to
* dynamic mode. If already in dynamic mode, a new dynamic query will be created to reflect the new
* buffer contents. This method will also take a conservative approach and invalidate if there is any
* uncommitted change in the backing table.
*/
private void maybeInvalidate()
{
Record snapshot = buffer.getSnapshot();
String entity = buffer.getEntityName();
if ((snapshot == null && lastFound != null) ||
(snapshot != null &&
(lastFound == null || !lastFound.primaryKey().equals(snapshot.primaryKey()))))
{
AdaptiveComponent comp = (AdaptiveComponent) components().get(0);
invalidate(comp);
return;
}
if (DirtyShareSupport.isEnabledCrossSession() || DirtyShareSupport.isEnabledIntraSession())
{
DirtyShareContext dirtyContext = buffer.getDirtyContext();
if (dirtyContext == null)
{
return;
}
if (dirtyContext.isEntityDirty(entity))
{
AdaptiveComponent comp = (AdaptiveComponent) components().get(0);
invalidate(comp);
}
}
}
}
}