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

       }
    }