Project

General

Profile

RandomAccessQuery.java

first implementation of FastFind algorithm - Ovidiu Maxiniuc, 07/07/2020 07:26 PM

Download (186 KB)

 
1
/*
2
** Module   : RandomAccessQuery.java
3
** Abstract : Backing query for converted queries which require dynamic record retrieval
4
**
5
** Copyright (c) 2004-2020, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- --JPRM-- --------------------------Description-------------------------------
8
** 001 ECF 20051007   @23259 Created initial version. Backing query for
9
**                           converted queries which require a dynamic
10
**                           record retrieval semantic.
11
** 002 ECF 20051111   @23386 Adapted to use with CompoundQuery. Implements
12
**                           Joinable, which is used by CompoundQuery when
13
**                           adding query components.
14
** 003 ECF 20051129   @23574 Added lenient-off-end mode. When in this
15
**                           mode, QueryOffEndExceptions thrown by the
16
**                           underlying buffer during next and previous
17
**                           operations are suppressed.
18
** 004 ECF 20051216   @23747 Added scrolling support. Now extends the new
19
**                           DynamicQuery base class, which provides
20
**                           cursoring support services. Modified
21
**                           retrieval logic to allow for repositioning.
22
** 005 ECF 20060104   @23828 Added accumulation support. Record retrieval
23
**                           methods first, last, next, and previous now
24
**                           update any registered accumulators after
25
**                           successfully retrieving a record.
26
** 006 GES 20060202   @24226 Removed main().
27
** 007 ECF 20060202   @24355 Added temp table support. Add buffer's
28
**                           multiplex ID as a query substitution argument
29
**                           for temp table queries.
30
** 008 ECF 20060208   @24365 Changed arguments processing in constructor.
31
**                           Store duplicates of BaseDataType arguments
32
**                           rather than references to the original
33
**                           objects.
34
** 009 ECF 20060215   @24723 Added support for client-side where clause
35
**                           processing. Major changes to execute() were
36
**                           necessary to support this. All constructors
37
**                           now accept an optional WhereExpression
38
**                           parameter. Also improved handling of query
39
**                           substitution parameter resolution to be more
40
**                           correct.
41
** 010 ECF 20060302   @25023 Add support for foreign key joins. Added new
42
**                           constructor variants which mirror existing
43
**                           constructors, but accept an additional,
44
**                           inverse DMO parameter. Modified query
45
**                           execution to integrate natural joins.
46
** 011 ECF 20060317   @25171 Fixed defect related to extent fields. Use
47
**                           mapping of query substitution parameters,
48
**                           which may have been reordered during HQL
49
**                           where clause preprocessing.
50
** 012 ECF 20060330   @25256 Removed final modifier from class. FindQuery
51
**                           extends this class.
52
** 013 ECF 20060424   @25696 Added new constructor variants. Each existing
53
**                           variant now has a counterpart which does not
54
**                           expect a WhereExpression parameter.
55
** 014 ECF 20060424   @25700 Roll back previous change.
56
** 015 ECF 20060529   @26645 Added initializeBuffer method. Resets buffer
57
**                           during query construction.
58
** 016 ECF 20060531   @26851 Replaced EndConditionException with
59
**                           QueryOffEndException. The latter is a more
60
**                           specific version of the former, used to
61
**                           indicate a query has run off the end of its
62
**                           result set.
63
** 017 ECF 20060610   @27102 Fixed lock processing defect. A record must
64
**                           not be stored in the buffer after a NO-WAIT
65
**                           record lock attempt fails.
66
** 018 ECF 20060621   @27506 Logging improvements.
67
** 019 ECF 20060713   @28046 Use new DBUtils class.
68
** 020 ECF 20060720   @28118 Minor changes driven by RecordBuffer naming
69
**                           changes. RecordBuffer.getLatestRecord() was
70
**                           changed to getSnapshot().
71
** 021 ECF 20060728   @28272 Minor change to accommodate RecordBuffer
72
**                           changes. Enclose RecordBuffer.setTempRecord()
73
**                           call in a try-catch block.
74
** 022 ECF 20060802   @28353 Added break value support. Implemented new
75
**                           getBreakValue() method.
76
** 023 ECF 20060803   @28404 Added updateBuffer() method. Allows
77
**                           subclasses to override setting current record
78
**                           into buffer.
79
** 024 ECF 20060925   @29964 Check for a pending error before retrieving a
80
**                           record. Retrieval is not attempted if an
81
**                           error is pending in the ErrorManager.
82
** 025 ECF 20061003   @30155 Call prepareFetch() instead of obsolete
83
**                           method checkPendingError(). Implemented
84
**                           releaseBuffers() to override do-nothing
85
**                           AbstractQuery implementation.
86
** 026 ECF 20061019   @30530 Changed reset() method. If the query is in
87
**                           scrolling mode, the cursor is reset as part
88
**                           of this method's processing.
89
** 027 ECF 20061023   @30609 Moved errorIfNull logic to AbstractQuery base
90
**                           class. Method setErrorIfNull() is now
91
**                           required by the P2JQuery interface and is
92
**                           implemented in AbstractQuery instead of here.
93
** 028 ECF 20061027   @30755 Replaced Object with DataModelObject for DMO
94
**                           arguments to methods/constructors. Required
95
**                           for compile-time type safety.
96
** 029 ECF 20061115   @31210 Changed call to set current buffer record.
97
**                           RecordBuffer API has changed to permit proper
98
**                           management of placeholder snapshot record.
99
**                           Added isResetSnapshot() method to allow
100
**                           subclasses to override this behavior.
101
** 030 ECF 20070216   @32165 Explicitly set errorIfNull to false. This is
102
**                           required to override the default setting of
103
**                           true made by AbstractQuery.
104
** 031 ECF 20070228   @32248 Optimized no-lock and temp table fetches. In
105
**                           these cases, it is not necessary to fetch the
106
**                           record in two steps. The no-lock case makes
107
**                           no guarantee that the record in memory has
108
**                           not been updated by other sessions. The temp
109
**                           table case guarantees that no other session
110
**                           can make an update to the record in memory,
111
**                           because a temp table record is inherently
112
**                           scoped to a session.
113
** 032 ECF 20070308   @32335 Fixed behavior for first/last. Upon failure
114
**                           to find a record, if not in looping mode,
115
**                           these methods must report a 'FIND FIRST/LAST
116
**                           failed...' message.
117
** 033 ECF 20070412   @32985 Retrofit to use RecordLockContext for record
118
**                           locking. Accommodate method name change in
119
**                           RecordBuffer: setCurrentRecord --> setRecord.
120
** 034 ECF 20070416   @33028 Integrated user interrupt handling.
121
** 035 ECF 20070504   @33410 Retrofitted load() method to use modified
122
**                           signature specified by Joinable.
123
** 036 ECF 20070508   @33460 Fixed executeImpl() to properly handle a
124
**                           validation exception. New requirement based
125
**                           on a change to RecordBuffer.flush().
126
** 037 ECF 20070518   @33684 Added getRecordBuffers() and recordBuffers().
127
**                           The former is required by the Joinable
128
**                           interface. The latter is required by the
129
**                           RecordChangeListener interface.
130
** 038 EVL 20070609   @34005 Adding explicit import of the class
131
**                           org.hibernate.type.Type to eliminate conflict
132
**                           with the same class from java.lang.reflect
133
**                           package to be able to compile for Java 6.
134
** 039 ECF 20070718   @34571 Fixed findNext()/findPrevious() and their
135
**                           callers. These methods were not properly
136
**                           updating locks when necessary.
137
** 040 ECF 20070713   @34858 Moved all CAN-FIND specific processing to the
138
**                           FindQuery subclass. Exposed some private
139
**                           methods as protected and refactored class to
140
**                           support this.
141
** 041 ECF 20070827   @35002 Minor optimization for temp table queries.
142
**                           Omit multiplex ID from query substitution
143
**                           parameters. HQLHelper now hard-codes the ID
144
**                           into the where clause, since it is known at
145
**                           the time the where clause is generated.
146
** 042 ECF 20070926   @35246 Fix placeholder record defect. In cases where
147
**                           the referenceRecord DMO is modified, we need
148
**                           to null it out and use the buffer's current
149
**                           snapshot as our placeholder record instead.
150
** 043 ECF 20071130   @36123 Removed isRefreshSnapshot() method. The
151
**                           RecordBuffer placeholder snapshot is no
152
**                           longer cleared by any query type when the
153
**                           query does not find a record to place in the
154
**                           buffer. Integrated generics.
155
** 044 ECF 20071204   @36266 Fixed off-end tracking. Replaced boolean flag
156
**                           with OffEnd enum to discern between off-end
157
**                           directions (i.e., off back vs off front of
158
**                           result set). Current off-end status and
159
**                           active sort index are reported to the record
160
**                           buffer when fetching a record. Off-end status
161
**                           is checked when searching for a new record.
162
** 045 ECF 20080112   @37006 Removed restriction of dealing only with IDs.
163
**                           This query can now handle both Persistables
164
**                           and primary key IDs when retrieving data.
165
**                           This is necessary when the full record is
166
**                           needed immediately, not just an ID, such as
167
**                           in presort cases.
168
** 046 CA  20080311   @37430 Removed the listener code from c'tor and
169
**                           added it to the new registerChangeListener()
170
**                           method.
171
** 047 ECF 20080310   @37482 Made changes necessary to support embedded,
172
**                           H2 database.
173
** 048 SVL 20080418   @38041 Added support for the inverse sorting.
174
** 049 SVL 20080421   @38082 Added support for legacy key join helper.
175
** 050 ECF 20080508   @38245 Override base lock type for temporary tables.
176
**                           This allows some downstream optimizations.
177
** 051 ECF 20080510   @38610 Implemented support for dirty database
178
**                           checking. Looks into a shared database of
179
**                           uncommitted updates and possibly overrides
180
**                           data found in primary database.
181
** 052 ECF 20080609   @38652 Removed overly aggressive optimization in
182
**                           executeImpl(). Queries with non-NO-LOCK types
183
**                           must also search among uncommitted updates,
184
**                           and locks must be acquired on dirty records.
185
** 053 ECF 20080610   @38690 Fixed dirty check processing in executeImpl()
186
**                           method. We were not correctly handling the
187
**                           case of finding a dirty version of the same
188
**                           DMO found in the primary database.
189
** 054 ECF 20080611   @38712 Fixed processResults(). When acquiring a lock
190
**                           for a dirty record, the record should be
191
**                           reloaded, in case it disappeared while we
192
**                           were waiting on the lock.
193
** 055 CA  20080618   @38868 When the JOINed foreign buffer does not refer
194
**                           any record, the query must return no result
195
**                           as it can not be executed.
196
** 056 SVL 20080630   @38985 Fixed executeImpl() for the case when a dirty
197
**                           record is inserted and modified.
198
** 057 SVL 20080730   @39222 Added dmoSorter instance variable.
199
** 058 CA  20080815   @39465 Support API change in DBUtils.
200
** 060 ECF 20081009   @40082 Integrated new support for inlining query
201
**                           substitution parameters. Queries supported by
202
**                           this class don't actually have their
203
**                           parameters inlined, but changes were needed
204
**                           to integrate HQLPreprocessor API changes and
205
**                           the ParameterIndices class.
206
** 061 ECF 20081105   @40312 Implemented registerRecordChangeListeners().
207
**                           Required by the Joinable interface.
208
** 062 ECF 20081215   @40917 Modified executeImpl(). A validation error
209
**                           ignores silent error mode, since the flushing
210
**                           of the previous record in the buffer is not
211
**                           masked by silent error mode.
212
** 063 ECF 20090303   @41599 Fixed to better support being a Joinable
213
**                           component of a CompoundQuery. Implemented
214
**                           RecordChangeListener, added getOffEnd()
215
**                           method. Replaced activeBundleKey Object with
216
**                           an int to support changes in HQLHelper.
217
** 064 ECF 20090317   @41627 Removed early calls to getHelper(). This
218
**                           method must not be invoked until the query is
219
**                           actually being executed, because of possible
220
**                           dependencies in HQLHelper.obtain() on the
221
**                           state of the query's substitution parameters.
222
**                           Specifically, a FieldReference parameter may
223
**                           not be backed by a record if this method is
224
**                           invoked arbitrarily early.
225
** 065 ECF 20090512   @42154 Modified prepareBuffer(). Now invokes flushAll()
226
**                           instead of flush() on backing buffer. This
227
**                           ensures all like buffers are allowed to flush
228
**                           their transient records properly.
229
** 066 ECF 20090603   @42596 Added support for Progress isolation leak quirk.
230
**                           Modified executeImpl() and processResults() to
231
**                           use new features in HQLPreprocessor and in
232
**                           DirtyShareContext to leak uncommitted changes to
233
**                           other sessions and to select appropriate record.
234
** 067 ECF 20090609   @42635 Fixed executeImpl(). We now evict, if possible,
235
**                           those DMOs read into the Hibernate Session, but
236
**                           not loaded into the record buffer. This includes
237
**                           DMOs whose uncommitted changes are leaked to
238
**                           other sessions early, and DMOs which are found in
239
**                           the primary database, but overridden by records
240
**                           found by the dirty share manager.
241
** 068 ECF 20090623   @42941 Fixed executeImpl(). Handle the case of a unique
242
**                           search finding information in the dirty database.
243
**                           Also added back multiplex ID as a substitution
244
**                           parameter for temp table queries.
245
** 069 ECF 20090703   @43058 Reimplemented load() method. This method now sets
246
**                           the backing buffer to unknown mode and throws
247
**                           MissingRecordException if a target record cannot
248
**                           be loaded.
249
** 070 ECF 20090716   @43221 Enabled lazy record buffer initialization. Added
250
**                           RecordBuffer.initialize() call in c'tor.
251
** 071 ECF 20090724   @43421 Added implicit transaction support. In various
252
**                           methods which execute queries, we push/pop an
253
**                           implicit transaction.
254
** 072 CA  20090731   @43470 Register the query for off-end listener 
255
**                           registration after block initialization (done
256
**                           only for block types FOR and FOR EACH). Added
257
**                           getOffEndListeners, which returns a list of all
258
**                           off-end listeners for all used buffers.
259
** 073 SVL 20091030   @44293 getOffEnd() function was made public in order to
260
**                           support API change into P2JQuery. 
261
** 074 OM  20130829          Added change detection to buffer on current() methods.
262
**                           Updated calls to match the new Persistence.load() signatures.
263
** 075 ECF 20131028          Import change.
264
** 076 OM  20140107          Added TODO for missing fatal error message.
265
** 077 SVL 20140604          Added support for index information field.
266
** 078 ECF 20140814          Replaced RecordBuffer.flushAll with flush in prepareBuffer method.
267
**                           Fixed dirty share logic to work with new flush/validate algorithm.
268
**                           Replaced Apache commons logging with J2SE logging.
269
** 079 OM  20150507          Removed listener from global scope of ChangeBroker.
270
** 080 ECF 20150801          Implemented new Joinable methods necessary for compound query
271
**                           optimization. Error message fix. Enabled database statistics.
272
** 081 ECF 20150906          Reimplemented silent error mode processing of query substitution
273
**                           parameters. Fixed NPE related to outer joins.
274
** 082 SVL 20151112          Continue normally if reposition failed in load().
275
** 083 EVL 20160223          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
276
** 084 SVL 20160314          Added setter for referenceRecord.
277
** 085 OM  20160316          Signature of DirtyShareContext.getDirtyInfo() has changed.
278
**     ECF 20160504          Pass new parameter to Persistence.load. Minor change to isIdOnly.
279
** 086 SVL 20160608          The query can handle null IDs (for scrolling compound queries
280
**                           with OUTER join).
281
** 087 ECF 20160615          Bias toward fetching full records when possible.
282
** 088 IAS 20160701          Fixed legacy name in the [565] error message.
283
** 089 IAS 20160714          Use legacy name instead of DMO alias.
284
** 090 OM  20160715          Added support for template records.
285
**                           Persistence.popImplicitTransaction() changed signature.
286
** 091 ECF 20160725          Fixed buffer lookup during HQL preprocessing.
287
** 092 ECF 20160823          Improved executeImpl to better detect missing records and avoid
288
**                           deadlock.
289
** 093 ECF 20160901          DirtyShareContext.isDirtyDelete signature change.
290
** 094 GES 20160804          Switched from WhereExpression to lambda usage.
291
**         20160919          All initialization is now done with a default constructor followed
292
**                           by a call to initialize(). This enables query instance members in
293
**                           converted code to be local variables that are effectively final.
294
**                           This is needed to support lambdas without moving queries to be
295
**                           instance members. Queries as instance members breaks recursion
296
**                           use cases.
297
** 095 CA  20161012          Fixed scope processing when the query is from a persistent 
298
**                           procedure, and is part of a QUERY resource.
299
** 096 ECF 20161014          Fixed implementation of current() method in the event there is no
300
**                           current record in the buffer to reload, and this is not considered
301
**                           an error in the context of the query (e.g., query represents an
302
**                           outer join and record can be null).
303
** 097 ECF 20171212          Reworked query substitution parameter error handling and error
304
**                           condition handling.
305
** 098 OM  20171129          releaseBuffers() is overwritten from P2JQuery.
306
**     ECF 20171217          Added workaround to executeImpl to avoid dirty share manager blowup,
307
**                           but this is not a long-term solution.
308
** 099 ECF 20180201          Removed initializeBuffer, which released the current record too
309
**                           aggressively.
310
** 100 OM  20180220          Fixed LEFT OUTER JOIN queries.
311
** 101 OM  20180321          Fixed NPE caused by a null substitution array.
312
** 102 ECF 20180418          Fixed sort clause generation in makeAdaptiveServerJoinComponent.
313
** 103 OM  20180515          Added dynamic filtering of results set.
314
** 104 OM  20180901          Improved dynamic filtering.
315
** 105 OM  20180918          Filtering is case insensitive and uses LIKE patterns.
316
**     OM  20180924          Filtering on decimal fields uses STRING(f, format) function.
317
** 106 OM  20181023          Added external buffers when making the adaptive server join
318
**                           components.
319
** 107 OM  20190330          Ignore close request when query not fully initialized.
320
** 108 ECF 20190813          Change to RecordBuffer.release and RecordBuffer.setRecord APIs.
321
** 109 SVL 20191002          Log if sort index was not found.
322
** 110 CA  20191005          A query must know if it is dynamic, when closing.
323
** 111 CA  20191009          Added where and sort clause translation in case of bound buffers.
324
** 112 CA  20200110          If the index is missing, assume the primary index (encountered when 
325
**                           the sort clause uses fields in a WORD index).
326
**     EVL 20200206          Adding NPE protection to initialize method.
327
** 113 ECF 20200419          Removed Hibernate dependencies.
328
*/
329

    
330
/*
331
** This program is free software: you can redistribute it and/or modify
332
** it under the terms of the GNU Affero General Public License as
333
** published by the Free Software Foundation, either version 3 of the
334
** License, or (at your option) any later version.
335
**
336
** This program is distributed in the hope that it will be useful,
337
** but WITHOUT ANY WARRANTY; without even the implied warranty of
338
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
339
** GNU Affero General Public License for more details.
340
**
341
** You may find a copy of the GNU Affero GPL version 3 at the following
342
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
343
** 
344
** Additional terms under GNU Affero GPL version 3 section 7:
345
** 
346
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
347
**   terms apply to the works covered under the License.  These additional terms
348
**   are non-permissive additional terms allowed under Section 7 of the GNU
349
**   Affero GPL version 3 and may not be removed by you.
350
** 
351
**   0. Attribution Requirement.
352
** 
353
**     You must preserve all legal notices or author attributions in the covered
354
**     work or Appropriate Legal Notices displayed by works containing the covered
355
**     work.  You may not remove from the covered work any author or developer
356
**     credit already included within the covered work.
357
** 
358
**   1. No License To Use Trademarks.
359
** 
360
**     This license does not grant any license or rights to use the trademarks
361
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
362
**     of Golden Code Development Corporation. You are not authorized to use the
363
**     name Golden Code, FWD, or the names of any author or contributor, for
364
**     publicity purposes without written authorization.
365
** 
366
**   2. No Misrepresentation of Affiliation.
367
** 
368
**     You may not represent yourself as Golden Code Development Corporation or FWD.
369
** 
370
**     You may not represent yourself for publicity purposes as associated with
371
**     Golden Code Development Corporation, FWD, or any author or contributor to
372
**     the covered work, without written authorization.
373
** 
374
**   3. No Misrepresentation of Source or Origin.
375
** 
376
**     You may not represent the covered work as solely your work.  All modified
377
**     versions of the covered work must be marked in a reasonable way to make it
378
**     clear that the modified work is not originating from Golden Code Development
379
**     Corporation or FWD.  All modified versions must contain the notices of
380
**     attribution required in this license.
381
*/
382

    
383
package com.goldencode.p2j.persist;
384

    
385
import java.io.*;
386
import java.lang.reflect.*;
387
import java.util.*;
388
import java.util.function.*;
389
import java.util.logging.*;
390
import com.goldencode.p2j.persist.dirty.*;
391
import com.goldencode.p2j.persist.event.*;
392
import com.goldencode.p2j.persist.lock.*;
393
import com.goldencode.p2j.security.*;
394
import com.goldencode.p2j.util.*;
395
import com.goldencode.p2j.util.LogHelper;
396
import com.goldencode.p2j.util.ErrorManager;
397
import com.goldencode.util.*;
398

    
399
/**
400
 * The backing query for converted queries which require a dynamic record
401
 * retrieval semantic.  This means that a single record is retrieved at a
402
 * time and the query manages state about that record's position relative
403
 * to others which also match the query's search criteria.  Furthermore,
404
 * any change to that record's data may change its position relative to other
405
 * matching records, and these changes must be supported while client code is
406
 * using the query to visit result records.  Client code can jump among
407
 * results randomly.  These moves are specified as either relative requests
408
 * (next, previous, current), or as absolute requests (first, last, unique).
409
 * <p>
410
 * This class slavishly implements the Progress semantic of fully dynamic
411
 * query iteration.  That is, each record is searched for and retrieved
412
 * individually, so that any change to an existing record can be reflected
413
 * immediately in the remaining navigation performed on the query.  This
414
 * means that an update to the current record may cause it to reappear later
415
 * in the query results, if the changes made to the record would cause it to
416
 * be sorted differently than when it was first retrieved (note that this can
417
 * cause infinite looping, just as in the pre-conversion, Progress code).
418
 * <p>
419
 * To implement the single-record retrieval semantic, this high level query
420
 * may issue multiple, database-level, select statements to the database for
421
 * each record retrieval attempt.  For details of how this is performed, see
422
 * {@link HQLHelper} and {@link HQLBundle}.  The result of a record retrieval
423
 * is a Data Model Object (DMO), which is stored in a {@link RecordBuffer}.
424
 * A record buffer is associated with this object at construction.
425
 * <p>
426
 * Because of this implementation requirement, <code>RandomAccessQuery</code>
427
 * represents <i>an extremely inefficient mechanism</i> by which to query a
428
 * database for a set of records.  As such, <b>it should not be used for new
429
 * development;  it is intended only to support legacy, converted code</b>,
430
 * insofar as that code may rely on the dynamic nature of the Progress
431
 * semantic.
432
 * <p>
433
 * This class implements the <code>Joinable</code> interface so that it can
434
 * be added as a component to a {@link CompoundQuery}.  When used in such a
435
 * capacity, the query should be configured to raise an end condition instead
436
 * of an error condition, in the case that a request to get the first, last,
437
 * or a unique record fails.  This will allow the compound query to continue
438
 * processing normally in this case.  This is accomplished by invoking {@link
439
 * #setErrorIfNull} with the parameter <code>true</code>.
440
 * <p>
441
 * When used to represent a Progress query (as created with DEFINE or OPEN
442
 * QUERY), {@link #setLenientOffEnd} must be set to <code>true</code>, which
443
 * suppresses end condition exceptions for <code>next</code> and
444
 * <code>previous</code> commands which run off the end of the query's
445
 * results.
446
 * <p>
447
 * <strong>Transaction Isolation (Dirty Reads)</strong>
448
 * <br>
449
 * This class supports Progress' peculiar form of transaction isolation, where
450
 * uncommitted updates, inserts, and deletes that affect database indexes are
451
 * visible across sessions.  This is emulated using a shared database (the
452
 * "dirty" database) which temporarily stores information about uncommitted
453
 * changes made by other contexts.  When those other sessions commit or roll
454
 * back their changes, this transient information is cleared from the dirty
455
 * database and is no longer visible.
456
 * <p>
457
 * The implementation of this feature requires that the results found in the
458
 * primary database backing a query be treated as provisional. Results found
459
 * in the dirty database may override, if it is determined that the "dirty"
460
 * result is the more correct result, given the query criteria.
461
 */
462
public class RandomAccessQuery
463
extends DynamicQuery
464
implements Joinable,
465
           QueryConstants,
466
           RecordChangeListener
467
{
468
   /** Logger */
469
   private static final Logger LOG = LogHelper.getLogger(RandomAccessQuery.class.getName());
470
   
471
   /** Template for "missing sort index" error message. */
472
   private static final String ERROR_LOCATE_SORT_INDEX = "Error locating index for sort phrase [%s]";
473
   
474
   /** Record buffer which backs this query */
475
   private RecordBuffer buffer;
476
   
477
   /** Helper object for dynamic join via a foreign key relation */
478
   private AbstractJoin join;
479
   
480
   /** Client-side where clause expression, if any */
481
   private Supplier<logical> whereExpr;
482
   
483
   /** Base substitution parameters for HQL queries before applying any dynamic filter. */
484
   private Object[] args;
485
   
486
   /** 
487
    * Base substitution parameters for HQL queries, including the dynamic filtering components,
488
    * if any.
489
    */
490
   private Object[] dfArgs;
491
   
492
   /** Lock type to apply to records retrieved */
493
   private LockType lockType;
494
   
495
   /** 
496
    * Original where clause (only stored until helper is built). It is only used for building the
497
    * {@code dfWhere} which also contains the information on eventual dynamic filters.
498
    */
499
   private String where = null;
500
   
501
   /**
502
    * Original where clause (only stored until helper is built), including the dynamic filtering
503
    * components, if any.
504
    */
505
   private String dfWhere = null;
506
   
507
   /** Original sort clause (only stored until helper is built) */
508
   private String sort = null;
509
   
510
   /** DMO sorter associated with query's sort clause */
511
   private DMOSorter dmoSorter = null;
512
   
513
   /** Name of index in use for this query */
514
   private String index = null;
515
   
516
   /** Index information string as it is returned by INDEX-INFORMATION. */
517
   private String indexInfo = null;
518
   
519
   /** Builds HQL statements for various Hibernate queries. */
520
   private HQLHelper helper = null;
521
   
522
   /** Cached helper for non-null arguments in left outer join queries. */
523
   private HQLHelper nonNullArgsHelper = null;
524
   
525
   /** HQL data necessary for the current query operation */
526
   private HQLBundle activeBundle = null;
527
   
528
   /** Key which indicates active bundle choice */
529
   private int activeBundleKey = NONE;
530
   
531
   /** {@code false} if running off end of results should raise error */
532
   private boolean lenientOffEnd = false;
533
   
534
   /** Substitution parameter values for the next query execution */
535
   private Object[] currentArgs = null;
536
   
537
   /** Record whose data defines current "place" in query results */
538
   private Record referenceRecord = null;
539
   
540
   /** Break value indicating a new sort band if non-<code>null</code> */
541
   private Object breakValue = null;
542
   
543
   /** Force query to retrieve full records, rather than primary keys only */
544
   private boolean fullRecords = false;
545
   
546
   /** Was last record found retrieved from the dirty database? */
547
   private boolean dirtyCopy = false;
548
   
549
   /** RecordChangeListener which nulls out placeholder reference record */
550
   private RecordChangeListener placeholderCleaner = null;
551
   
552
   /** Determines if this will be unregistered from ChangeBroker on cleanup. */
553
   private boolean unregisterOnCleanup = false;
554
   
555
   /**
556
    * The list of arguments for currently applied dynamic filters.
557
    * Note that its size may be smaller than the number of dynamic filters because the null /
558
    * unknown values were automatically converted to {@code is null} so they lack the argument.
559
    */
560
   private List<Object> dynamicFilterArgs = null;
561
   
562
   /**
563
    * The list of dynamic filters. These are filters added dynamically, at runtime and can also be
564
    * removed dynamically.
565
    */
566
   private Map<FieldReference, BaseDataType> dynamicFilters = null;
567
   
568
   /**
569
    * The list of format used by dynamic filters. These are filters added dynamically, at runtime
570
    * and can also be removed dynamically.
571
    */
572
   private Map<FieldReference, String> dynamicFormats = null;
573
   
574
   /** The static {@code ContextLocal} container for cache.  */
575
   private static final ContextLocal<HashMap<FastFindKey, Record>> ffContextCache = 
576
      new ContextLocal<HashMap<FastFindKey, Record>>() {
577
         @Override
578
         protected HashMap<FastFindKey, Record> initialValue() {
579
            // for the moment the cache is just a naive map
580
            return new HashMap<>();
581
         }
582
      };
583
   
584
   /** Is FastFind enabled? */
585
   private boolean ffEnable = true;
586
   
587
   /** The cached FastFind cache bounded to this context. */
588
   private final HashMap<FastFindKey, Record> ffCache = ffContextCache.get();
589
   
590
   /**
591
    * Default constructor. Initialization is deferred until <code>initialize()</code> is
592
    * called.
593
    */
594
   public RandomAccessQuery()
595
   {
596
   }
597
   
598
   /**
599
    * Initialization logic which defaults record lock type to <code>LockType.SHARE</code>.
600
    * <p>
601
    * This code is intentionally separated from the constructor. The purpose of this separation
602
    * is to allow construction to occur in the prior scope while initialization can still occur
603
    * within a method or lambda that is inside the next block's scope.
604
    *
605
    * @param    dmo
606
    *           Data model object which determines the record buffer into which
607
    *           records are retrieved.
608
    * @param    where
609
    *           Where clause, using HQL syntax.  May be <code>null</code>.
610
    * @param    whereExpr
611
    *           Client-side where clause expression.  May be <code>null</code>.
612
    * @param    sort
613
    *           Order by clause, using HQL syntax.  May be <code>null</code>
614
    *           only if query is to be navigated only using one of the
615
    *           <code>unique</code> method variants.
616
    *            
617
    * @return   This query instance.
618
    */
619
   public RandomAccessQuery initialize(DataModelObject   dmo,
620
                                       String            where,
621
                                       Supplier<logical> whereExpr,
622
                                       String            sort)
623
   {
624
      return initialize(dmo, where, whereExpr, sort, null, null, null, LockType.SHARE);
625
   }
626
   
627
   /**
628
    * Initialization logic which defaults record lock type to <code>LockType.SHARE</code>.
629
    * <p>
630
    * This code is intentionally separated from the constructor. The purpose of this separation
631
    * is to allow construction to occur in the prior scope while initialization can still occur
632
    * within a method or lambda that is inside the next block's scope.
633
    *
634
    * @param    dmo
635
    *           Data model object which determines the record buffer into which
636
    *           records are retrieved.
637
    * @param    where
638
    *           Where clause, using HQL syntax.  May be <code>null</code>.
639
    * @param    whereExpr
640
    *           Client-side where clause expression.  May be <code>null</code>.
641
    * @param    indexInfo
642
    *           Index information string as it is returned by INDEX-INFORMATION.
643
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
644
    *           you don't need debug information about selected indexes.
645
    * @param    sort
646
    *           Order by clause, using HQL syntax.  May be <code>null</code>
647
    *           only if query is to be navigated only using one of the
648
    *           <code>unique</code> method variants.
649
    *            
650
    * @return   This query instance.
651
    */
652
   public RandomAccessQuery initialize(DataModelObject   dmo,
653
                                       String            where,
654
                                       Supplier<logical> whereExpr,
655
                                       String            sort,
656
                                       String            indexInfo)
657
   {
658
      return initialize(dmo, where, whereExpr, sort, indexInfo, null, null, LockType.SHARE);
659
   }
660
   
661
   /**
662
    * Initialization logic which is used when joining to another table using a foreign
663
    * key.  Defaults record lock type to <code>LockType.SHARE</code>.
664
    * <p>
665
    * This code is intentionally separated from the constructor. The purpose of this separation
666
    * is to allow construction to occur in the prior scope while initialization can still occur
667
    * within a method or lambda that is inside the next block's scope.
668
    *
669
    * @param    dmo
670
    *           Data model object which determines the record buffer into which
671
    *           records are retrieved.
672
    * @param    where
673
    *           Where clause, using HQL syntax.  May be <code>null</code>.
674
    * @param    whereExpr
675
    *           Client-side where clause expression.  May be <code>null</code>.
676
    * @param    sort
677
    *           Order by clause, using HQL syntax.  May be <code>null</code>
678
    *           only if query is to be navigated only using one of the
679
    *           <code>unique</code> method variants.
680
    * @param    inverse
681
    *           DMO to which this query should join via a foreign relation.
682
    *           May be <code>null</code>.
683
    *            
684
    * @return   This query instance.
685
    */
686
   public RandomAccessQuery initialize(DataModelObject   dmo,
687
                                       String            where,
688
                                       Supplier<logical> whereExpr,
689
                                       String            sort,
690
                                       DataModelObject   inverse)
691
   {
692
      return initialize(dmo, where, whereExpr, sort, null, inverse, null, LockType.SHARE);
693
   }
694
   
695
   /**
696
    * Initialization logic which is used when joining to another table using a foreign
697
    * key.  Defaults record lock type to <code>LockType.SHARE</code>.
698
    * <p>
699
    * This code is intentionally separated from the constructor. The purpose of this separation
700
    * is to allow construction to occur in the prior scope while initialization can still occur
701
    * within a method or lambda that is inside the next block's scope.
702
    *
703
    * @param    dmo
704
    *           Data model object which determines the record buffer into which
705
    *           records are retrieved.
706
    * @param    where
707
    *           Where clause, using HQL syntax.  May be <code>null</code>.
708
    * @param    whereExpr
709
    *           Client-side where clause expression.  May be <code>null</code>.
710
    * @param    sort
711
    *           Order by clause, using HQL syntax.  May be <code>null</code>
712
    *           only if query is to be navigated only using one of the
713
    *           <code>unique</code> method variants.
714
    * @param    indexInfo
715
    *           Index information string as it is returned by INDEX-INFORMATION.
716
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
717
    *           you don't need debug information about selected indexes.
718
    * @param    inverse
719
    *           DMO to which this query should join via a foreign relation.
720
    *           May be <code>null</code>.
721
    *            
722
    * @return   This query instance.
723
    */
724
   public RandomAccessQuery initialize(DataModelObject   dmo,
725
                                       String            where,
726
                                       Supplier<logical> whereExpr,
727
                                       String            sort,
728
                                       String            indexInfo,
729
                                       DataModelObject   inverse)
730
   {
731
      return initialize(dmo, where, whereExpr, sort, indexInfo, inverse, null, LockType.SHARE);
732
   }
733
   
734
   /**
735
    * Initialization logic which defaults record lock type to
736
    * <code>LockType.SHARE</code> and accepts default substitution parameters.
737
    * <p>
738
    * This code is intentionally separated from the constructor. The purpose of this separation
739
    * is to allow construction to occur in the prior scope while initialization can still occur
740
    * within a method or lambda that is inside the next block's scope.
741
    *
742
    * @param    dmo
743
    *           Data model object which determines the record buffer into which
744
    *           records are retrieved.
745
    * @param    where
746
    *           Where clause, using HQL syntax.  May be <code>null</code>.
747
    * @param    whereExpr
748
    *           Client-side where clause expression.  May be <code>null</code>.
749
    * @param    sort
750
    *           Order by clause, using HQL syntax.  May be <code>null</code>
751
    *           only if query is to be navigated only using one of the
752
    *           <code>unique</code> method variants.
753
    * @param    args
754
    *           Substitution parameters for HQL queries.  These will be used
755
    *           if not overridden by a record retrieval method.
756
    *            
757
    * @return   This query instance.
758
    *           
759
    * @throws   IllegalArgumentException
760
    *           if any substitution argument provided is not of a supported
761
    *           type.
762
    */
763
   public RandomAccessQuery initialize(DataModelObject   dmo,
764
                                       String            where,
765
                                       Supplier<logical> whereExpr,
766
                                       String            sort,
767
                                       Object[]          args)
768
   {
769
      return initialize(dmo, where, whereExpr, sort, null, null, args, LockType.SHARE);
770
   }
771
   
772
   /**
773
    * Initialization logic which defaults record lock type to
774
    * <code>LockType.SHARE</code> and accepts default substitution parameters.
775
    * <p>
776
    * This code is intentionally separated from the constructor. The purpose of this separation
777
    * is to allow construction to occur in the prior scope while initialization can still occur
778
    * within a method or lambda that is inside the next block's scope.
779
    *
780
    * @param    dmo
781
    *           Data model object which determines the record buffer into which
782
    *           records are retrieved.
783
    * @param    where
784
    *           Where clause, using HQL syntax.  May be <code>null</code>.
785
    * @param    whereExpr
786
    *           Client-side where clause expression.  May be <code>null</code>.
787
    * @param    sort
788
    *           Order by clause, using HQL syntax.  May be <code>null</code>
789
    *           only if query is to be navigated only using one of the
790
    *           <code>unique</code> method variants.
791
    * @param    indexInfo
792
    *           Index information string as it is returned by INDEX-INFORMATION.
793
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
794
    *           you don't need debug information about selected indexes.
795
    * @param    args
796
    *           Substitution parameters for HQL queries.  These will be used
797
    *           if not overridden by a record retrieval method.
798
    *            
799
    * @return   This query instance.
800
    *           
801
    * @throws   IllegalArgumentException
802
    *           if any substitution argument provided is not of a supported
803
    *           type.
804
    */
805
   public RandomAccessQuery initialize(DataModelObject   dmo,
806
                                       String            where,
807
                                       Supplier<logical> whereExpr,
808
                                       String            sort,
809
                                       String            indexInfo,
810
                                       Object[]          args)
811
   {
812
      return initialize(dmo, where, whereExpr, sort, indexInfo, null, args, LockType.SHARE);
813
   }
814
   
815
   /**
816
    * Initialization logic which is used when joining to another table using a foreign
817
    * key.  Defaults record lock type to <code>LockType.SHARE</code> and
818
    * accepts default substitution parameters.
819
    * <p>
820
    * This code is intentionally separated from the constructor. The purpose of this separation
821
    * is to allow construction to occur in the prior scope while initialization can still occur
822
    * within a method or lambda that is inside the next block's scope.
823
    *
824
    * @param    dmo
825
    *           Data model object which determines the record buffer into which
826
    *           records are retrieved.
827
    * @param    where
828
    *           Where clause, using HQL syntax.  May be <code>null</code>.
829
    * @param    whereExpr
830
    *           Client-side where clause expression.  May be <code>null</code>.
831
    * @param    sort
832
    *           Order by clause, using HQL syntax.  May be <code>null</code>
833
    *           only if query is to be navigated only using one of the
834
    *           <code>unique</code> method variants.
835
    * @param    inverse
836
    *           DMO to which this query should join via a foreign relation.
837
    *           May be <code>null</code>.
838
    * @param    args
839
    *           Substitution parameters for HQL queries.  These will be used
840
    *           if not overridden by a record retrieval method.
841
    *            
842
    * @return   This query instance.
843
    *           
844
    * @throws   IllegalArgumentException
845
    *           if any substitution argument provided is not of a supported
846
    *           type.
847
    */
848
   public RandomAccessQuery initialize(DataModelObject   dmo,
849
                                       String            where,
850
                                       Supplier<logical> whereExpr,
851
                                       String            sort,
852
                                       DataModelObject   inverse,
853
                                       Object[]          args)
854
   {
855
      return initialize(dmo, where, whereExpr, sort, null, inverse, args, LockType.SHARE);
856
   }
857
   
858
   /**
859
    * Initialization logic which is used when joining to another table using a foreign
860
    * key.  Defaults record lock type to <code>LockType.SHARE</code> and
861
    * accepts default substitution parameters.
862
    * <p>
863
    * This code is intentionally separated from the constructor. The purpose of this separation
864
    * is to allow construction to occur in the prior scope while initialization can still occur
865
    * within a method or lambda that is inside the next block's scope.
866
    *
867
    * @param    dmo
868
    *           Data model object which determines the record buffer into which
869
    *           records are retrieved.
870
    * @param    where
871
    *           Where clause, using HQL syntax.  May be <code>null</code>.
872
    * @param    whereExpr
873
    *           Client-side where clause expression.  May be <code>null</code>.
874
    * @param    sort
875
    *           Order by clause, using HQL syntax.  May be <code>null</code>
876
    *           only if query is to be navigated only using one of the
877
    *           <code>unique</code> method variants.
878
    * @param    indexInfo
879
    *           Index information string as it is returned by INDEX-INFORMATION.
880
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
881
    *           you don't need debug information about selected indexes.
882
    * @param    inverse
883
    *           DMO to which this query should join via a foreign relation.
884
    *           May be <code>null</code>.
885
    * @param    args
886
    *           Substitution parameters for HQL queries.  These will be used
887
    *           if not overridden by a record retrieval method.
888
    *            
889
    * @return   This query instance.
890
    *           
891
    * @throws   IllegalArgumentException
892
    *           if any substitution argument provided is not of a supported
893
    *           type.
894
    */
895
   public RandomAccessQuery initialize(DataModelObject   dmo,
896
                                       String            where,
897
                                       Supplier<logical> whereExpr,
898
                                       String            sort,
899
                                       String            indexInfo,
900
                                       DataModelObject   inverse,
901
                                       Object[]          args)
902
   {
903
      return initialize(dmo, where, whereExpr, sort, indexInfo, inverse, args, LockType.SHARE);
904
   }
905
   
906
   /**
907
    * Initialization logic which sets an explicit, default, record lock type.
908
    * <p>
909
    * This code is intentionally separated from the constructor. The purpose of this separation
910
    * is to allow construction to occur in the prior scope while initialization can still occur
911
    * within a method or lambda that is inside the next block's scope.
912
    *
913
    * @param    dmo
914
    *           Data model object which determines the record buffer into which
915
    *           records are retrieved.
916
    * @param    where
917
    *           Where clause, using HQL syntax.  May be <code>null</code>.
918
    * @param    whereExpr
919
    *           Client-side where clause expression.  May be <code>null</code>.
920
    * @param    sort
921
    *           Order by clause, using HQL syntax.  May be <code>null</code>
922
    *           only if query is to be navigated only using one of the
923
    *           <code>unique</code> method variants.
924
    * @param    lockType
925
    *           Lock type to apply to records retrieved, if not overridden by
926
    *           a record retrieval method.
927
    *            
928
    * @return   This query instance.
929
    */
930
   public RandomAccessQuery initialize(DataModelObject   dmo,
931
                                       String            where,
932
                                       Supplier<logical> whereExpr,
933
                                       String            sort,
934
                                       LockType          lockType)
935
   {
936
      return initialize(dmo, where, whereExpr, sort, null, null, null, lockType);
937
   }
938
   
939
   /**
940
    * Initialization logic which sets an explicit, default, record lock type.
941
    * <p>
942
    * This code is intentionally separated from the constructor. The purpose of this separation
943
    * is to allow construction to occur in the prior scope while initialization can still occur
944
    * within a method or lambda that is inside the next block's scope.
945
    *
946
    * @param    dmo
947
    *           Data model object which determines the record buffer into which
948
    *           records are retrieved.
949
    * @param    where
950
    *           Where clause, using HQL syntax.  May be <code>null</code>.
951
    * @param    whereExpr
952
    *           Client-side where clause expression.  May be <code>null</code>.
953
    * @param    sort
954
    *           Order by clause, using HQL syntax.  May be <code>null</code>
955
    *           only if query is to be navigated only using one of the
956
    *           <code>unique</code> method variants.
957
    * @param    indexInfo
958
    *           Index information string as it is returned by INDEX-INFORMATION.
959
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
960
    *           you don't need debug information about selected indexes.
961
    * @param    lockType
962
    *           Lock type to apply to records retrieved, if not overridden by
963
    *           a record retrieval method.
964
    *            
965
    * @return   This query instance.
966
    */
967
   public RandomAccessQuery initialize(DataModelObject   dmo,
968
                                       String            where,
969
                                       Supplier<logical> whereExpr,
970
                                       String            sort,
971
                                       String            indexInfo,
972
                                       LockType          lockType)
973
   {
974
      return initialize(dmo, where, whereExpr, sort, indexInfo, null, null, lockType);
975
   }
976
   
977
   /**
978
    * Initialization logic which is used when joining to another table using a foreign
979
    * key.  Sets an explicit, default, record lock type.
980
    * <p>
981
    * This code is intentionally separated from the constructor. The purpose of this separation
982
    * is to allow construction to occur in the prior scope while initialization can still occur
983
    * within a method or lambda that is inside the next block's scope.
984
    *
985
    * @param    dmo
986
    *           Data model object which determines the record buffer into which
987
    *           records are retrieved.
988
    * @param    where
989
    *           Where clause, using HQL syntax.  May be <code>null</code>.
990
    * @param    whereExpr
991
    *           Client-side where clause expression.  May be <code>null</code>.
992
    * @param    sort
993
    *           Order by clause, using HQL syntax.  May be <code>null</code>
994
    *           only if query is to be navigated only using one of the
995
    *           <code>unique</code> method variants.
996
    * @param    inverse
997
    *           DMO to which this query should join via a foreign relation.
998
    *           May be <code>null</code>.
999
    * @param    lockType
1000
    *           Lock type to apply to records retrieved, if not overridden by
1001
    *           a record retrieval method.
1002
    *            
1003
    * @return   This query instance.
1004
    */
1005
   public RandomAccessQuery initialize(DataModelObject   dmo,
1006
                                       String            where,
1007
                                       Supplier<logical> whereExpr,
1008
                                       String            sort,
1009
                                       DataModelObject   inverse,
1010
                                       LockType          lockType)
1011
   {
1012
      return initialize(dmo, where, whereExpr, sort, null, inverse, null, lockType);
1013
   }
1014
   
1015
   /**
1016
    * Initialization logic which is used when joining to another table using a foreign
1017
    * key.  Sets an explicit, default, record lock type.
1018
    * <p>
1019
    * This code is intentionally separated from the constructor. The purpose of this separation
1020
    * is to allow construction to occur in the prior scope while initialization can still occur
1021
    * within a method or lambda that is inside the next block's scope.
1022
    *
1023
    * @param    dmo
1024
    *           Data model object which determines the record buffer into which
1025
    *           records are retrieved.
1026
    * @param    where
1027
    *           Where clause, using HQL syntax.  May be <code>null</code>.
1028
    * @param    whereExpr
1029
    *           Client-side where clause expression.  May be <code>null</code>.
1030
    * @param    sort
1031
    *           Order by clause, using HQL syntax.  May be <code>null</code>
1032
    *           only if query is to be navigated only using one of the
1033
    *           <code>unique</code> method variants.
1034
    * @param    indexInfo
1035
    *           Index information string as it is returned by INDEX-INFORMATION.
1036
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
1037
    *           you don't need debug information about selected indexes.
1038
    * @param    inverse
1039
    *           DMO to which this query should join via a foreign relation.
1040
    *           May be <code>null</code>.
1041
    * @param    lockType
1042
    *           Lock type to apply to records retrieved, if not overridden by
1043
    *           a record retrieval method.
1044
    *            
1045
    * @return   This query instance.
1046
    */
1047
   public RandomAccessQuery initialize(DataModelObject   dmo,
1048
                                       String            where,
1049
                                       Supplier<logical> whereExpr,
1050
                                       String            sort,
1051
                                       String            indexInfo,
1052
                                       DataModelObject   inverse,
1053
                                       LockType          lockType)
1054
   {
1055
      return initialize(dmo, where, whereExpr, sort, indexInfo, inverse, null, lockType);
1056
   }
1057
   
1058
   /**
1059
    * Initialization logic which sets an explicit, default, record lock type and
1060
    * accepts default substitution parameters.
1061
    * <p>
1062
    * This code is intentionally separated from the constructor. The purpose of this separation
1063
    * is to allow construction to occur in the prior scope while initialization can still occur
1064
    * within a method or lambda that is inside the next block's scope.
1065
    *
1066
    * @param    dmo
1067
    *           Data model object which determines the record buffer into which
1068
    *           records are retrieved.
1069
    * @param    where
1070
    *           Where clause, using HQL syntax.  May be <code>null</code>.
1071
    * @param    whereExpr
1072
    *           Client-side where clause expression.  May be <code>null</code>.
1073
    * @param    sort
1074
    *           Order by clause, using HQL syntax.  May be <code>null</code>
1075
    *           only if query is to be navigated only using one of the
1076
    *           <code>unique</code> method variants.
1077
    * @param    args
1078
    *           Substitution parameters for HQL queries.  These will be used
1079
    *           if not overridden by a record retrieval method.
1080
    * @param    lockType
1081
    *           Lock type to apply to records retrieved, if not overridden by
1082
    *           a record retrieval method.
1083
    *            
1084
    * @return   This query instance.
1085
    *
1086
    * @throws   IllegalArgumentException
1087
    *           if any substitution argument provided is not of a supported
1088
    *           type.
1089
    */
1090
   public RandomAccessQuery initialize(DataModelObject   dmo,
1091
                                       String            where,
1092
                                       Supplier<logical> whereExpr,
1093
                                       String            sort,
1094
                                       Object[]          args,
1095
                                       LockType          lockType)
1096
   {
1097
      return initialize(dmo, where, whereExpr, sort, null, null, args, lockType);
1098
   }
1099
   
1100
   /**
1101
    * Initialization logic which sets an explicit, default, record lock type and
1102
    * accepts default substitution parameters.
1103
    * <p>
1104
    * This code is intentionally separated from the constructor. The purpose of this separation
1105
    * is to allow construction to occur in the prior scope while initialization can still occur
1106
    * within a method or lambda that is inside the next block's scope.
1107
    *
1108
    * @param    dmo
1109
    *           Data model object which determines the record buffer into which
1110
    *           records are retrieved.
1111
    * @param    where
1112
    *           Where clause, using HQL syntax.  May be <code>null</code>.
1113
    * @param    whereExpr
1114
    *           Client-side where clause expression.  May be <code>null</code>.
1115
    * @param    sort
1116
    *           Order by clause, using HQL syntax.  May be <code>null</code>
1117
    *           only if query is to be navigated only using one of the
1118
    *           <code>unique</code> method variants.
1119
    * @param    indexInfo
1120
    *           Index information string as it is returned by INDEX-INFORMATION.
1121
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
1122
    *           you don't need debug information about selected indexes.
1123
    * @param    args
1124
    *           Substitution parameters for HQL queries.  These will be used
1125
    *           if not overridden by a record retrieval method.
1126
    * @param    lockType
1127
    *           Lock type to apply to records retrieved, if not overridden by
1128
    *           a record retrieval method.
1129
    *            
1130
    * @return   This query instance.
1131
    *
1132
    * @throws   IllegalArgumentException
1133
    *           if any substitution argument provided is not of a supported
1134
    *           type.
1135
    */
1136
   public RandomAccessQuery initialize(DataModelObject   dmo,
1137
                                       String            where,
1138
                                       Supplier<logical> whereExpr,
1139
                                       String            sort,
1140
                                       String            indexInfo,
1141
                                       Object[]          args,
1142
                                       LockType          lockType)
1143
   {
1144
      return initialize(dmo, where, whereExpr, sort, indexInfo, null, args, lockType);
1145
   }
1146
   
1147
   /**
1148
    * Initialization logic which is used when joining to another table using a foreign
1149
    * key.  Sets an explicit, default, record lock type and accepts default
1150
    * substitution parameters.
1151
    * <p>
1152
    * This code is intentionally separated from the constructor. The purpose of this separation
1153
    * is to allow construction to occur in the prior scope while initialization can still occur
1154
    * within a method or lambda that is inside the next block's scope.
1155
    *
1156
    * @param    dmo
1157
    *           Data model object which determines the record buffer into which
1158
    *           records are retrieved.
1159
    * @param    where
1160
    *           Where clause, using HQL syntax.  May be <code>null</code>.
1161
    * @param    whereExpr
1162
    *           Client-side where clause expression.  May be <code>null</code>.
1163
    * @param    sort
1164
    *           Order by clause, using HQL syntax.  May be <code>null</code>
1165
    *           only if query is to be navigated only using one of the
1166
    *           <code>unique</code> method variants.
1167
    * @param    inverse
1168
    *           DMO to which this query should join via a foreign relation.
1169
    *           May be <code>null</code>.
1170
    * @param    args
1171
    *           Substitution parameters for HQL queries.  These will be used
1172
    *           if not overridden by a record retrieval method.
1173
    * @param    lockType
1174
    *           Lock type to apply to records retrieved, if not overridden by
1175
    *           a record retrieval method.
1176
    *            
1177
    * @return   This query instance.
1178
    *
1179
    * @throws   IllegalArgumentException
1180
    *           if any substitution argument provided is not of a supported
1181
    *           type.
1182
    */
1183
   public RandomAccessQuery initialize(DataModelObject   dmo,
1184
                                       String            where,
1185
                                       Supplier<logical> whereExpr,
1186
                                       String            sort,
1187
                                       DataModelObject   inverse,
1188
                                       Object[]          args,
1189
                                       LockType          lockType)
1190
   {
1191
      return initialize(dmo, where, whereExpr, sort, null, inverse, args, lockType);
1192
   }
1193
   
1194
   /**
1195
    * Initialization logic which is used when joining to another table using a foreign
1196
    * key.  Sets an explicit, default, record lock type and accepts default
1197
    * substitution parameters.
1198
    * <p>
1199
    * This code is intentionally separated from the constructor. The purpose of this separation
1200
    * is to allow construction to occur in the prior scope while initialization can still occur
1201
    * within a method or lambda that is inside the next block's scope.
1202
    *
1203
    * @param    dmo
1204
    *           Data model object which determines the record buffer into which
1205
    *           records are retrieved.
1206
    * @param    where
1207
    *           Where clause, using HQL syntax.  May be <code>null</code>.
1208
    * @param    whereExpr
1209
    *           Client-side where clause expression.  May be <code>null</code>.
1210
    * @param    sort
1211
    *           Order by clause, using HQL syntax.  May be <code>null</code>
1212
    *           only if query is to be navigated only using one of the
1213
    *           <code>unique</code> method variants.
1214
    * @param    indexInfo
1215
    *           Index information string as it is returned by INDEX-INFORMATION.
1216
    *           May be <code>null</code> if it is not an OPEN QUERY case or if
1217
    *           you don't need debug information about selected indexes.
1218
    * @param    inverse
1219
    *           DMO to which this query should join via a foreign relation.
1220
    *           May be <code>null</code>.
1221
    * @param    args
1222
    *           Substitution parameters for HQL queries.  These will be used
1223
    *           if not overridden by a record retrieval method.
1224
    * @param    lockType
1225
    *           Lock type to apply to records retrieved, if not overridden by
1226
    *           a record retrieval method.
1227
    *            
1228
    * @return   This query instance.
1229
    *
1230
    * @throws   IllegalArgumentException
1231
    *           if any substitution argument provided is not of a supported
1232
    *           type.
1233
    */
1234
   public RandomAccessQuery initialize(DataModelObject   dmo,
1235
                                       String            where,
1236
                                       Supplier<logical> whereExpr,
1237
                                       String            sort,
1238
                                       String            indexInfo,
1239
                                       DataModelObject   inverse,
1240
                                       Object[]          args,
1241
                                       LockType          lockType)
1242
   {
1243
      this.buffer = ((BufferReference) dmo).buffer();
1244
      RecordBuffer defBuffer = ((BufferReference) dmo).definition();
1245
      
1246
      if (!buffersMatch(buffer, defBuffer))
1247
      {
1248
         sort = translateSort(buffer, defBuffer, sort);
1249
         ArrayList<RecordBuffer> binding = new ArrayList<>();
1250
         binding.add(buffer);
1251
         ArrayList<RecordBuffer> definition = new ArrayList<>();
1252
         definition.add(defBuffer);
1253
         
1254
         where = translateWhere(binding, definition, where);
1255
      }
1256
      
1257
      boolean abort = false;
1258
      
1259
      if (!AbstractQuery.preprocessSubstitutionArguments(buffer.getBufferManager(), args))
1260
      {
1261
         setFatalError();
1262
         abort = true;
1263
      }
1264
      
1265
      if (buffer.isReadonly())
1266
      {
1267
         // TODO: readonly buffers cannot be used in any FIND statement
1268
         //       but can be used in CAN-FIND functions
1269
      }
1270
      
1271
      buffer.initialize();
1272
      
1273
      if (buffer.getDirtyContext() != null)
1274
      {
1275
         // Get index name, which is needed for dirty checking work.
1276
         try
1277
         {
1278
            IndexHelper indexHelper = buffer.getIndexHelper();
1279
            this.index = indexHelper.getIndexForSort(buffer, sort);
1280
            if (index == null)
1281
            {
1282
               P2JIndex p2JavaIndex = indexHelper.getPrimaryIndex(
1283
                                           buffer.getDMOImplementationClass().getCanonicalName());
1284
               if (p2JavaIndex != null)
1285
               {
1286
                  index = p2JavaIndex.getName();
1287
               }
1288
            }
1289
            this.dmoSorter = indexHelper.getSorterForSortPhrase(buffer, sort);
1290
         }
1291
         catch (PersistenceException exc)
1292
         {
1293
            if (LOG.isLoggable(Level.SEVERE))
1294
            {
1295
               LOG.log(Level.SEVERE, String.format(ERROR_LOCATE_SORT_INDEX, sort), exc);
1296
            }
1297
         }
1298
         
1299
         if (index == null && LOG.isLoggable(Level.SEVERE))
1300
         {
1301
            LOG.log(Level.SEVERE, String.format(ERROR_LOCATE_SORT_INDEX, sort));
1302
         }
1303
      }
1304
      
1305
      if (inverse != null)
1306
      {
1307
         if (Persistence.isForeignKeysEnabled())
1308
         {
1309
            join = new DynamicJoin(buffer, ((BufferReference) inverse).buffer());
1310
         }
1311
         else
1312
         {
1313
            join = new DynamicLegacyKeyJoin(buffer, ((BufferReference) inverse).buffer());
1314
         }
1315
      }
1316
      else
1317
      {
1318
         join = null;
1319
      }
1320
      this.where = where;
1321
      this.dfWhere = where;
1322
      this.sort = sort;
1323
      this.whereExpr = whereExpr;
1324
      this.args = args;
1325
      this.dfArgs = args;
1326
      this.indexInfo = indexInfo;
1327
      
1328
      // override lock type if this is a temporary buffer;  this allows some downstream optimizations
1329
      this.lockType = (buffer.isTemporary() ? LockType.NONE : lockType);
1330
      
1331
      setErrorIfNull(false);
1332
      resolveArgs();
1333
      registerChangeListener();
1334
      
1335
      TransactionManager.registerOffEndQuery(this);
1336
      
1337
      return this;
1338
   }
1339
   
1340
   /**
1341
    * Force the query to retrieve full records, rather than primary keys only.
1342
    */
1343
   public void setFullRecords()
1344
   {
1345
      this.fullRecords = true;
1346
   }
1347
   
1348
   /**
1349
    * Set the behavior of this query as a record retrieval request moves off
1350
    * the end of its results.  If set to lenient mode, invocations of
1351
    * <code>next</code> and <code>previous</code> which do not find a record
1352
    * do not raise a <code>QueryOffEndException</code>.  By default, this
1353
    * would normally raise the exception.
1354
    *
1355
    * @param   lenientOffEnd
1356
    *          <code>true</code> to suppress <code>QueryOffEndException</code>
1357
    *          for <code>next</code> and <code>previous</code> requests;
1358
    *          <code>false</code> to permit these exceptions.
1359
    */
1360
   public void setLenientOffEnd(boolean lenientOffEnd)
1361
   {
1362
      this.lenientOffEnd = lenientOffEnd;
1363
   }
1364
   
1365
   /**
1366
    * Navigate to the first record which meets the query criteria and retrieve
1367
    * it into its record buffer.  Use the default substitution parameters and
1368
    * lock type.
1369
    *
1370
    * @throws  ErrorConditionException
1371
    *          if an error occurred during record retrieval or if no record
1372
    *          could be found when operating in standalone mode.
1373
    * @throws  QueryOffEndException
1374
    *          if no record could be found when operating in compound query
1375
    *          mode.
1376
    */
1377
   public void first()
1378
   {
1379
      first(getCurrentArgs(), lockType);
1380
   }
1381
   
1382
   /**
1383
    * Navigate to the first record which meets the query criteria and retrieve
1384
    * it into its record buffer.  Use the default lock type, but override the
1385
    * default substitution parameters with those provided.
1386
    *
1387
    * @param   values
1388
    *          Substitution values to use when issuing HQL queries.
1389
    *
1390
    * @throws  ErrorConditionException
1391
    *          if an error occurred during record retrieval or if no record
1392
    *          could be found when operating in standalone mode.
1393
    * @throws  QueryOffEndException
1394
    *          if no record could be found when operating in compound query
1395
    *          mode.
1396
    */
1397
   public void first(Object[] values)
1398
   {
1399
      first(values, lockType);
1400
   }
1401
   
1402
   /**
1403
    * Navigate to the first record which meets the query criteria and retrieve
1404
    * it into its record buffer.  Use the default substitution parameters, but
1405
    * override the default lock type with that provided.
1406
    *
1407
    * @param   lockType
1408
    *          Lock type to acquire for the retrieved record.
1409
    *
1410
    * @throws  ErrorConditionException
1411
    *          if an error occurred during record retrieval or if no record
1412
    *          could be found when operating in standalone mode.
1413
    * @throws  QueryOffEndException
1414
    *          if no record could be found when operating in compound query
1415
    *          mode.
1416
    */
1417
   public void first(LockType lockType)
1418
   {
1419
      first(getCurrentArgs(), lockType);
1420
   }
1421
   
1422
   /**
1423
    * Navigate to the first record which meets the query criteria and retrieve
1424
    * it into its record buffer.  Override both the default substitution
1425
    * parameters, and the default lock type with those provided.
1426
    *
1427
    * @param   values
1428
    *          Substitution values to use when issuing HQL queries.
1429
    * @param   lockType
1430
    *          Lock type to acquire for the retrieved record.
1431
    *
1432
    * @throws  ErrorConditionException
1433
    *          if an error occurred during record retrieval or if no record
1434
    *          could be found when operating in standalone mode.
1435
    * @throws  QueryOffEndException
1436
    *          if no record could be found when operating in compound query
1437
    *          mode.
1438
    */
1439
   public void first(Object[] values, LockType lockType)
1440
   {
1441
      if (!prepareFetch())
1442
      {
1443
         return;
1444
      }
1445
      
1446
      if (outer)
1447
      {
1448
         // reset now the helper. Another one will be created if any of the arguments are null.
1449
         // Otherwise the not-null helper will be used if already constructed.
1450
         helper = null;
1451
      }
1452
      
1453
      if (getHelper().getHQLPreprocessor().isFindByRowid()) 
1454
      {
1455
         // direct record access using recid/rowid is behaving like unique for all search types 
1456
         unique(values, lockType);
1457
         return;
1458
      }
1459
      
1460
      // If a lock type is not specified, assume the query's lock type.
1461
      if (lockType == null)
1462
      {
1463
         lockType = this.lockType;
1464
      }
1465
      
1466
      Record dmo = null;
1467
      
1468
      try
1469
      {
1470
         // if query is scrolling, check the cached result list first
1471
         boolean scrolling = (cursor != null);
1472
         if (scrolling)
1473
         {
1474
            dmo = loadByValue(cursor.getFirst(), lockType);
1475
         }
1476
         
1477
         // if not scrolling or wasn't in the cached result list, attempt fast find algorithm
1478
         FastFindKey ffk = null;
1479
         if (dmo == null)
1480
         {
1481
            if (ffEnable)
1482
            {
1483
               ffk = new FastFindKey(FIRST, index, values);
1484
               dmo = ffCache.get(ffk);
1485
               
1486
               if (dmo != null)
1487
               {
1488
                  ffk = null; // prevent putting it back
1489
               }
1490
            }
1491
         }
1492
         
1493
         // lastly, perform the query on the database (and cache the result if scrolling)
1494
         if (dmo == null)
1495
         {
1496
            activateFirst();
1497
            dmo = execute(values, lockType, false, true);
1498
            if (scrolling)
1499
            {
1500
               cursor.addResultFirst(getIDs(dmo));
1501
            }
1502
         }
1503
         
1504
         if (ffk != null && dmo != null)
1505
         {
1506
            // save the result
1507
            ffCache.put(ffk, dmo);
1508
         }
1509
         
1510
         // store result in the record buffer, even if null
1511
         boolean errNull = isErrorIfNull();
1512
         OffEnd offEnd = (dmo == null ? OffEnd.BACK : OffEnd.NONE);
1513
         updateBuffer(dmo, lockType, errNull, offEnd);
1514
         
1515
         // Reset reference record.
1516
         referenceRecord = null;
1517
         
1518
         // Reset dirty flag.
1519
         dirtyCopy = false;
1520
         
1521
         if (dmo == null && errNull)
1522
         {
1523
            errorFindFirstLast();
1524
         }
1525
         
1526
         if (dmo != null)
1527
         {
1528
            // Notify accumulators of an iteration.
1529
            accumulate();
1530
         }
1531
      }
1532
      catch (QueryOffEndException exc)
1533
      {
1534
         // rethrow without buffer release; this catch block is here so we don't hit the
1535
         // ErrorConditionException catch block (QOEE's ancestor) and release the buffer
1536
         throw exc;
1537
      }
1538
      catch (ErrorConditionException exc)
1539
      {
1540
         // if the exception is set to force an override of silent error mode behavior, it is
1541
         // because we encountered a validation error while flushing a record from the buffer
1542
         // to make way for the query result; don't try to release/flush the invalid record
1543
         // again here
1544
         if (!exc.isForce())
1545
         {
1546
            buffer.release(false);
1547
         }
1548
         
1549
         throw exc;
1550
      }
1551
   }
1552
   
1553
   /**
1554
    * Navigate to the last record which meets the query criteria and retrieve
1555
    * it into its record buffer.  Use the default substitution parameters and
1556
    * lock type.
1557
    *
1558
    * @throws  ErrorConditionException
1559
    *          if an error occurred during record retrieval or if no record
1560
    *          could be found when operating in standalone mode.
1561
    * @throws  QueryOffEndException
1562
    *          if no record could be found when operating in compound query
1563
    *          mode.
1564
    */
1565
   public void last()
1566
   {
1567
      last(getCurrentArgs(), lockType);
1568
   }
1569
   
1570
   /**
1571
    * Navigate to the last record which meets the query criteria and retrieve
1572
    * it into its record buffer.  Use the default lock type, but override the
1573
    * default substitution parameters with those provided.
1574
    *
1575
    * @param   values
1576
    *          Substitution values to use when issuing HQL queries.
1577
    *
1578
    * @throws  ErrorConditionException
1579
    *          if an error occurred during record retrieval or if no record
1580
    *          could be found when operating in standalone mode.
1581
    * @throws  QueryOffEndException
1582
    *          if no record could be found when operating in compound query
1583
    *          mode.
1584
    */
1585
   public void last(Object[] values)
1586
   {
1587
      last(values, lockType);
1588
   }
1589
   
1590
   /**
1591
    * Navigate to the last record which meets the query criteria and retrieve
1592
    * it into its record buffer.  Use the default substitution parameters, but
1593
    * override the default lock type with that provided.
1594
    *
1595
    * @param   lockType
1596
    *          Lock type to acquire for the retrieved record.
1597
    *
1598
    * @throws  ErrorConditionException
1599
    *          if an error occurred during record retrieval or if no record
1600
    *          could be found when operating in standalone mode.
1601
    * @throws  QueryOffEndException
1602
    *          if no record could be found when operating in compound query
1603
    *          mode.
1604
    */
1605
   public void last(LockType lockType)
1606
   {
1607
      last(getCurrentArgs(), lockType);
1608
   }
1609
   
1610
   /**
1611
    * Navigate to the last record which meets the query criteria and retrieve
1612
    * it into its record buffer.  Override both the default substitution
1613
    * parameters, and the default lock type with those provided.
1614
    *
1615
    * @param   values
1616
    *          Substitution values to use when issuing HQL queries.
1617
    * @param   lockType
1618
    *          Lock type to acquire for the retrieved record.
1619
    *
1620
    * @throws  ErrorConditionException
1621
    *          if an error occurred during record retrieval or if no record
1622
    *          could be found when operating in standalone mode.
1623
    * @throws  QueryOffEndException
1624
    *          if no record could be found when operating in compound query
1625
    *          mode.
1626
    */
1627
   public void last(Object[] values, LockType lockType)
1628
   {
1629
      if (!prepareFetch())
1630
      {
1631
         return;
1632
      }
1633
      
1634
      if (outer)
1635
      {
1636
         // reset now the helper. Another one will be created if any of the arguments are null.
1637
         // Otherwise the not-null helper will be used if already constructed.
1638
         helper = null;
1639
      }
1640
      
1641
      if (getHelper().getHQLPreprocessor().isFindByRowid())
1642
      {
1643
         // direct record access using recid/rowid is behaving like unique for all search types 
1644
         unique(values, lockType);
1645
         return;
1646
      }
1647
      
1648
      // If a lock type is not specified, assume the query's lock type.
1649
      if (lockType == null)
1650
      {
1651
         lockType = this.lockType;
1652
      }
1653
      
1654
      Record dmo = null;
1655
      FastFindKey ffk = null;
1656
      
1657
      try
1658
      {
1659
         // If query is scrolling, check the cached result list first.
1660
         boolean scrolling = (cursor != null);
1661
         if (scrolling)
1662
         {
1663
            dmo = loadByValue(cursor.getLast(), lockType);
1664
         }
1665
         
1666
         // if not scrolling or wasn't in the cached result list, attempt fast find algorithm
1667
         if (dmo == null)
1668
         {
1669
            if (ffEnable)
1670
            {
1671
               ffk = new FastFindKey(LAST, index, values);
1672
               dmo = ffCache.get(ffk);
1673
               if (dmo != null)
1674
               {
1675
                  ffk = null;
1676
               }
1677
            }
1678
         }
1679
         
1680
         // lastly, perform the query on the database (and cache the result if scrolling)
1681
         if (dmo == null)
1682
         {
1683
            activateLast();
1684
            dmo = execute(values, lockType, false, true);
1685
            if (scrolling)
1686
            {
1687
               cursor.addResultLast(getIDs(dmo));
1688
            }
1689
         }
1690
         
1691
         // Store result in the record buffer, even if null.
1692
         boolean errNull = isErrorIfNull();
1693
         OffEnd offEnd = (dmo == null ? OffEnd.BACK : OffEnd.NONE);
1694
         updateBuffer(dmo, lockType, errNull, offEnd);
1695
         
1696
         // save the ff result, if the case
1697
         if (ffk != null && dmo != null)
1698
         {
1699
            ffCache.put(ffk, dmo);
1700
         }
1701
         
1702
         // reset reference record
1703
         referenceRecord = null;
1704
         
1705
         // Reset dirty flag.
1706
         dirtyCopy = false;
1707
         
1708
         if (dmo == null && errNull)
1709
         {
1710
            errorFindFirstLast();
1711
         }
1712
         
1713
         if (dmo != null)
1714
         {
1715
            // Notify accumulators of an iteration.
1716
            accumulate();
1717
         }
1718
      }
1719
      catch (QueryOffEndException exc)
1720
      {
1721
         // rethrow without buffer release; this catch block is here so we don't hit the
1722
         // ErrorConditionException catch block (QOEE's ancestor) and release the buffer
1723
         throw exc;
1724
      }
1725
      catch (ErrorConditionException exc)
1726
      {
1727
         // if the exception is set to force an override of silent error mode behavior, it is
1728
         // because we encountered a validation error while flushing a record from the buffer
1729
         // to make way for the query result; don't try to release/flush the invalid record
1730
         // again here
1731
         if (!exc.isForce())
1732
         {
1733
            buffer.release(false);
1734
         }
1735
         
1736
         throw exc;
1737
      }
1738
   }
1739
   
1740
   /**
1741
    * Navigate to the next record which meets the query criteria and retrieve
1742
    * it into its record buffer.  Use the default substitution parameters and
1743
    * lock type.
1744
    * <p>
1745
    * Use the record most recently loaded into the backing buffer as the
1746
    * reference point when determining the next record to visit.  If no
1747
    * record has yet been loaded into the buffer, retrieve the first record
1748
    * which meets the query criteria.
1749
    *
1750
    * @throws  ErrorConditionException
1751
    *          if an error occurred during record retrieval.
1752
    * @throws  QueryOffEndException
1753
    *          if no record could be found.
1754
    */
1755
   public void next()
1756
   {
1757
      next(getCurrentArgs(), lockType);
1758
   }
1759
   
1760
   /**
1761
    * Navigate to the next record which meets the query criteria and retrieve
1762
    * it into its record buffer.  Use the default lock type, but override the
1763
    * default substitution parameters with those provided.
1764
    * <p>
1765
    * Use the record most recently loaded into the backing buffer as the
1766
    * reference point when determining the next record to visit.  If no
1767
    * record has yet been loaded into the buffer, retrieve the first record
1768
    * which meets the query criteria.
1769
    *
1770
    * @param   values
1771
    *          Substitution values to use when issuing HQL queries.
1772
    *
1773
    * @throws  ErrorConditionException
1774
    *          if an error occurred during record retrieval.
1775
    * @throws  QueryOffEndException
1776
    *          if no record could be found.
1777
    */
1778
   public void next(Object[] values)
1779
   {
1780
      next(values, lockType);
1781
   }
1782
   
1783
   /**
1784
    * Navigate to the next record which meets the query criteria and retrieve
1785
    * it into its record buffer.  Use the default substitution parameters, but
1786
    * override the default lock type with that provided.
1787
    * <p>
1788
    * Use the record most recently loaded into the backing buffer as the
1789
    * reference point when determining the next record to visit.  If no
1790
    * record has yet been loaded into the buffer, retrieve the first record
1791
    * which meets the query criteria.
1792
    *
1793
    * @param   lockType
1794
    *          Lock type to acquire for the retrieved record.
1795
    *
1796
    * @throws  ErrorConditionException
1797
    *          if an error occurred during record retrieval.
1798
    * @throws  QueryOffEndException
1799
    *          if no record could be found.
1800
    */
1801
   public void next(LockType lockType)
1802
   {
1803
      next(getCurrentArgs(), lockType);
1804
   }
1805
   
1806
   /**
1807
    * Navigate to the next record which meets the query criteria and retrieve
1808
    * it into its record buffer.  Override both the default substitution
1809
    * parameters, and the default lock type with those provided.
1810
    * <p>
1811
    * Use the record most recently loaded into the backing buffer as the
1812
    * reference point when determining the next record to visit.  If no
1813
    * record has yet been loaded into the buffer, retrieve the first record
1814
    * which meets the query criteria.
1815
    *
1816
    * @param   values
1817
    *          Substitution values to use when issuing HQL queries.
1818
    * @param   lockType
1819
    *          Lock type to acquire for the retrieved record.
1820
    *
1821
    * @throws  ErrorConditionException
1822
    *          if an error occurred during record retrieval.
1823
    * @throws  QueryOffEndException
1824
    *          if no record could be found and <code>lenientOffEnd</code> is
1825
    *          <code>false</code>.
1826
    */
1827
   public void next(Object[] values, LockType lockType)
1828
   {
1829
      if (!prepareFetch())
1830
      {
1831
         return;
1832
      }
1833
      
1834
      if (outer)
1835
      {
1836
         // reset now the helper. Another one will be created if any of the arguments are null.
1837
         // Otherwise the not-null helper will be used if already constructed.
1838
         helper = null;
1839
      }
1840
      
1841
      if (getHelper().getHQLPreprocessor().isFindByRowid())
1842
      {
1843
         // direct record access using recid/rowid is behaving like unique for all search types 
1844
         unique(values, lockType);
1845
         return;
1846
      }
1847
      
1848
      // If a lock type is not specified, assume the query's lock type.
1849
      if (lockType == null)
1850
      {
1851
         lockType = this.lockType;
1852
      }
1853
      
1854
      Record dmo = null;
1855
      
1856
      try
1857
      {
1858
         // If query is scrolling, check the cached result list first.
1859
         boolean scrolling = (cursor != null);
1860
         if (scrolling)
1861
         {
1862
            dmo = loadByValue(cursor.getNext(), lockType);
1863
         }
1864
         
1865
         // If not scrolling or wasn't in the cached result list, perform the
1866
         // query and cache the result (if scrolling).
1867
         if (dmo == null)
1868
         {
1869
            boolean isFirst =
1870
               (buffer.getSnapshot() == null &&
1871
                referenceRecord == null      &&
1872
                buffer.getOffEnd(getHelper().getSortIndex(), inverseSorting) == OffEnd.FRONT);
1873
            dmo = findNext(values, lockType, true);
1874
            if (scrolling)
1875
            {
1876
               if (isFirst)
1877
               {
1878
                  cursor.addResultFirst(getIDs(dmo));
1879
               }
1880
               else
1881
               {
1882
                  cursor.addResultNext(getIDs(dmo));
1883
               }
1884
            }
1885
         }
1886
         
1887
         // Store result in the record buffer, even if null.
1888
         OffEnd offEnd = (dmo == null ? OffEnd.BACK : OffEnd.NONE);
1889
         updateBuffer(dmo, lockType, false, offEnd);
1890
         referenceRecord = dmo;
1891
         
1892
         // Notify accumulators of an iteration.
1893
         accumulate();
1894
      }
1895
      catch (QueryOffEndException exc)
1896
      {
1897
         if (!lenientOffEnd)
1898
         {
1899
            throw exc;
1900
         }
1901
      }
1902
      catch (ErrorConditionException exc)
1903
      {
1904
         // if the exception is set to force an override of silent error mode behavior, it is
1905
         // because we encountered a validation error while flushing a record from the buffer
1906
         // to make way for the query result; don't try to release/flush the invalid record
1907
         // again here
1908
         if (!exc.isForce())
1909
         {
1910
            buffer.release(false);
1911
         }
1912
         
1913
         throw exc;
1914
      }
1915
   }
1916
   
1917
   /**
1918
    * Navigate to the previous record which meets the query criteria and
1919
    * retrieve it into its record buffer.  Use the default substitution
1920
    * parameters and lock type.
1921
    * <p>
1922
    * Use the record most recently loaded into the backing buffer as the
1923
    * reference point when determining the previous record to visit.  If no
1924
    * record has yet been loaded into the buffer, retrieve the last record
1925
    * which meets the query criteria.
1926
    *
1927
    * @throws  ErrorConditionException
1928
    *          if an error occurred during record retrieval.
1929
    * @throws  QueryOffEndException
1930
    *          if no record could be found.
1931
    */
1932
   public void previous()
1933
   {
1934
      previous(getCurrentArgs(), lockType);
1935
   }
1936
   
1937
   /**
1938
    * Navigate to the previous record which meets the query criteria and
1939
    * retrieve it into its record buffer.  Use the default lock type, but
1940
    * override the default substitution parameters with those provided.
1941
    * <p>
1942
    * Use the record most recently loaded into the backing buffer as the
1943
    * reference point when determining the previous record to visit.  If no
1944
    * record has yet been loaded into the buffer, retrieve the last record
1945
    * which meets the query criteria.
1946
    *
1947
    * @param   values
1948
    *          Substitution values to use when issuing HQL queries.
1949
    *
1950
    * @throws  ErrorConditionException
1951
    *          if an error occurred during record retrieval.
1952
    * @throws  QueryOffEndException
1953
    *          if no record could be found.
1954
    */
1955
   public void previous(Object[] values)
1956
   {
1957
      previous(values, lockType);
1958
   }
1959
   
1960
   /**
1961
    * Navigate to the previous record which meets the query criteria and
1962
    * retrieve it into its record buffer.  Use the default substitution
1963
    * parameters, but override the default lock type with that provided.
1964
    * <p>
1965
    * Use the record most recently loaded into the backing buffer as the
1966
    * reference point when determining the previous record to visit.  If no
1967
    * record has yet been loaded into the buffer, retrieve the last record
1968
    * which meets the query criteria.
1969
    *
1970
    * @param   lockType
1971
    *          Lock type to acquire for the retrieved record.
1972
    *
1973
    * @throws  ErrorConditionException
1974
    *          if an error occurred during record retrieval.
1975
    * @throws  QueryOffEndException
1976
    *          if no record could be found.
1977
    */
1978
   public void previous(LockType lockType)
1979
   {
1980
      previous(getCurrentArgs(), lockType);
1981
   }
1982
   
1983
   /**
1984
    * Navigate to the previous record which meets the query criteria and
1985
    * retrieve it into its record buffer.  Override both the default
1986
    * substitution parameters, and the default lock type with those provided.
1987
    * <p>
1988
    * Use the record most recently loaded into the backing buffer as the
1989
    * reference point when determining the previous record to visit.  If no
1990
    * record has yet been loaded into the buffer, retrieve the last record
1991
    * which meets the query criteria.
1992
    *
1993
    * @param   values
1994
    *          Substitution values to use when issuing HQL queries.
1995
    * @param   lockType
1996
    *          Lock type to acquire for the retrieved record.
1997
    *
1998
    * @throws  ErrorConditionException
1999
    *          if an error occurred during record retrieval.
2000
    * @throws  QueryOffEndException
2001
    *          if no record could be found and <code>lenientOffEnd</code> is
2002
    *          <code>false</code>.
2003
    */
2004
   public void previous(Object[] values, LockType lockType)
2005
   {
2006
      if (!prepareFetch())
2007
      {
2008
         return;
2009
      }
2010
      
2011
      if (outer)
2012
      {
2013
         // reset now the helper. Another one will be created if any of the arguments are null.
2014
         // Otherwise the not-null helper will be used if already constructed.
2015
         helper = null;
2016
      }
2017
      
2018
      if (getHelper().getHQLPreprocessor().isFindByRowid())
2019
      {
2020
         // direct record access using recid/rowid is behaving like unique for all search types 
2021
         unique(values, lockType);
2022
         return;
2023
      }
2024
      
2025
      // If a lock type is not specified, assume the query's lock type.
2026
      if (lockType == null)
2027
      {
2028
         lockType = this.lockType;
2029
      }
2030
      
2031
      Record dmo = null;
2032
      
2033
      try
2034
      {
2035
         // If query is scrolling, check the cached result list first.
2036
         boolean scrolling = (cursor != null);
2037
         if (scrolling)
2038
         {
2039
            dmo = loadByValue(cursor.getPrevious(), lockType);
2040
         }
2041
         
2042
         // If not scrolling or wasn't in the cached result list, perform the
2043
         // query and cache the result (if scrolling).
2044
         if (dmo == null)
2045
         {
2046
            OffEnd offEnd = buffer.getOffEnd(getHelper().getSortIndex(), inverseSorting);
2047
            boolean isLast =
2048
               offEnd == OffEnd.BACK ||
2049
               (buffer.getSnapshot() == null &&
2050
                referenceRecord == null      &&
2051
                offEnd == OffEnd.FRONT);
2052
            dmo = findPrevious(values, lockType, true);
2053
            
2054
            if (scrolling)
2055
            {
2056
               if (isLast)
2057
               {
2058
                  cursor.addResultLast(getIDs(dmo));
2059
               }
2060
               else
2061
               {
2062
                  cursor.addResultPrevious(getIDs(dmo));
2063
               }
2064
            }
2065
         }
2066
         
2067
         // Store result in the record buffer, even if null.
2068
         OffEnd offEnd = (dmo == null ? OffEnd.FRONT : OffEnd.NONE);
2069
         updateBuffer(dmo, lockType, false, offEnd);
2070
         referenceRecord = dmo;
2071
         
2072
         // Notify accumulators of an iteration.
2073
         accumulate();
2074
      }
2075
      catch (QueryOffEndException exc)
2076
      {
2077
         if (!lenientOffEnd)
2078
         {
2079
            throw exc;
2080
         }
2081
      }
2082
      catch (ErrorConditionException exc)
2083
      {
2084
         // if the exception is set to force an override of silent error mode behavior, it is
2085
         // because we encountered a validation error while flushing a record from the buffer
2086
         // to make way for the query result; don't try to release/flush the invalid record
2087
         // again here
2088
         if (!exc.isForce())
2089
         {
2090
            buffer.release(false);
2091
         }
2092
         
2093
         throw exc;
2094
      }
2095
      finally
2096
      {
2097
         // Reset dirty flag.
2098
         dirtyCopy = false;
2099
      }
2100
   }
2101
   
2102
   /**
2103
    * Reload the record most recently loaded into the backing record buffer,
2104
    * if any.  Use the default lock type.
2105
    *
2106
    * @throws  ErrorConditionException
2107
    *          if an error occurred during record reload.
2108
    */
2109
   public void current()
2110
   {
2111
      current(lockType);
2112
   }
2113
   
2114
   /**
2115
    * Reload the record most recently loaded into the backing record buffer,
2116
    * if any.  Override the default lock type.
2117
    *
2118
    * @param   lockType
2119
    *          New lock type to apply to the record.
2120
    *
2121
    * @throws  ErrorConditionException
2122
    *          if an error occurred during record reload, or the lock could
2123
    *          not be transitioned to the requested type.
2124
    */
2125
   public void current(LockType lockType)
2126
   {
2127
      if (!prepareFetch())
2128
      {
2129
         return;
2130
      }
2131
      
2132
      buffer.armCurrentChanged();
2133
      buffer.reload(lockType, isErrorIfNull());
2134
      buffer.updateCurrentChanged();
2135
   }
2136
   
2137
   /**
2138
    * Navigate to a particular record which meets the query criteria and
2139
    * retrieve it into its record buffer.  No more than one record may match
2140
    * the criteria.  Use the default substitution parameters and lock type.
2141
    *
2142
    * @throws  ErrorConditionException
2143
    *          if an error occurred during record retrieval or if more than
2144
    *          one record is found which match the criteria.
2145
    * @throws  QueryOffEndException
2146
    *          if no record could be found when operating in compound query
2147
    *          mode.
2148
    */
2149
   public void unique()
2150
   {
2151
      unique(getCurrentArgs(), lockType);
2152
   }
2153
   
2154
   /**
2155
    * Navigate to a particular record which meets the query criteria and
2156
    * retrieve it into its record buffer.  No more than one record may match
2157
    * the criteria.  Use the default lock type, but override the default
2158
    * substitution parameters with those provided.
2159
    *
2160
    * @param   values
2161
    *          Substitution values to use when issuing HQL queries.
2162
    *
2163
    * @throws  ErrorConditionException
2164
    *          if an error occurred during record retrieval or if more than
2165
    *          one record is found which match the criteria.
2166
    * @throws  QueryOffEndException
2167
    *          if no record could be found when operating in compound query
2168
    *          mode.
2169
    */
2170
   public void unique(Object[] values)
2171
   {
2172
      unique(values, lockType);
2173
   }
2174
   
2175
   /**
2176
    * Navigate to a particular record which meets the query criteria and
2177
    * retrieve it into its record buffer.  No more than one record may match
2178
    * the criteria.  Use the default substitution parameters, but override
2179
    * the default lock type with that provided.
2180
    *
2181
    * @param   lockType
2182
    *          Lock type to acquire for the retrieved record.
2183
    *
2184
    * @throws  ErrorConditionException
2185
    *          if an error occurred during record retrieval or if more than
2186
    *          one record is found which match the criteria.
2187
    * @throws  QueryOffEndException
2188
    *          if no record could be found when operating in compound query
2189
    *          mode.
2190
    */
2191
   public void unique(LockType lockType)
2192
   {
2193
      unique(getCurrentArgs(), lockType);
2194
   }
2195
   
2196
   /**
2197
    * Navigate to a particular record which meets the query criteria and
2198
    * retrieve it into its record buffer.  No more than one record may match
2199
    * the criteria.  Override both the default substitution parameters, and
2200
    * the default lock type with those provided.
2201
    *
2202
    * @param   values
2203
    *          Substitution values to use when issuing HQL queries.
2204
    * @param   lockType
2205
    *          Lock type to acquire for the retrieved record.
2206
    *
2207
    * @throws  ErrorConditionException
2208
    *          if an error occurred during record retrieval or if more than
2209
    *          one record is found which match the criteria.
2210
    * @throws  QueryOffEndException
2211
    *          if no record could be found when operating in compound query
2212
    *          mode.
2213
    */
2214
   public void unique(Object[] values, LockType lockType)
2215
   {
2216
      if (!prepareFetch())
2217
      {
2218
         return;
2219
      }
2220
      
2221
      if (outer)
2222
      {
2223
         // reset now the helper. Another one will be created if any of the arguments are null.
2224
         // Otherwise the not-null helper will be used if already constructed.
2225
         helper = null;
2226
      }
2227
      
2228
      HQLHelper cachedHelper = getHelper();
2229
      HQLPreprocessor hqlPreprocessor = cachedHelper.getHQLPreprocessor();
2230
      if (!buffer.isTemporary() &&
2231
          cachedHelper.isFindTemplate(values, buffer.getDMOImplementationClass()))
2232
      {
2233
         buffer.loadTemplateRecord(hqlPreprocessor.getFindByRowid(values));
2234
         referenceRecord = null; // reset reference record
2235
         dirtyCopy = false;      // reset dirty flag
2236
         return;
2237
      }
2238
      else if (hqlPreprocessor.isFindByRowid() &&
2239
               hqlPreprocessor.getFindByRowid(values) < 0)
2240
      {
2241
         // according to article P12127, looking for an illegal rowid is equivalent to releasing
2242
         // the buffer. Backstage, error 18 is printed in database log but this is not a effect
2243
         // we need to duplicate in P2J. 
2244
         buffer.release(true);
2245
         referenceRecord = null; // reset reference record
2246
         dirtyCopy = false;      // reset dirty flag
2247
         if (isErrorIfNull())
2248
         {
2249
            buffer.errorNotOnFile();
2250
         }
2251
         return;
2252
      }
2253
      
2254
      if (lockType == null)
2255
      {
2256
         lockType = this.lockType;
2257
      }
2258
      
2259
      try
2260
      {
2261
         FastFindKey ffk = null;
2262
         Record dmo = null;
2263
         
2264
         if (ffEnable)
2265
         {
2266
            ffk = new FastFindKey(UNIQUE, index, values);
2267
            dmo = ffCache.get(ffk);
2268
            if (dmo != null)
2269
            {
2270
               ffk = null;
2271
            }
2272
         }
2273
         if (dmo == null)
2274
         {
2275
            activateUnique();
2276
            dmo = execute(values, lockType, true, true);
2277
         }
2278
         
2279
         boolean errNull = isErrorIfNull();
2280
         OffEnd offEnd = (dmo == null ? OffEnd.BACK : OffEnd.NONE);
2281
         updateBuffer(dmo, lockType, errNull, offEnd);
2282
         
2283
         // save the ff result, if the case
2284
         if (ffk != null && dmo != null)
2285
         {
2286
            ffCache.put(ffk, dmo);
2287
         }
2288
         
2289
         dirtyCopy = false;
2290
         if (dmo == null && errNull)
2291
         {
2292
            buffer.errorNotOnFile();
2293
         }
2294
      }
2295
      catch (ErrorConditionException exc)
2296
      {
2297
         // if the exception is set to force an override of silent error mode behavior, it is
2298
         // because we encountered a validation error while flushing a record from the buffer
2299
         // to make way for the query result; don't try to release/flush the invalid record again here
2300
         if (!exc.isForce())
2301
         {
2302
            buffer.release(false);
2303
         }
2304
         
2305
         throw exc;
2306
      }
2307
   }
2308
   
2309
   /**
2310
    * Set the buffer backing the query to unknown mode.
2311
    *
2312
    * @see     RecordBuffer#setUnknownMode
2313
    */
2314
   public void setUnknownRecord()
2315
   {
2316
      buffer.setUnknownMode();
2317
   }
2318
   
2319
   /**
2320
    * Reset the query by clearing its state to a known default.  This is
2321
    * used to ensure that subsequent requests for FIRST and LAST record
2322
    * retrievals operate on a known query/buffer state.
2323
    *
2324
    * @param   resolveArgs
2325
    *          <code>true</code> if query substitution arguments should be
2326
    *          resolved as part of the reset;  <code>false</code> if existing
2327
    *          argument values should be used for the next query execution.
2328
    */
2329
   public void reset(boolean resolveArgs)
2330
   {
2331
      if (resolveArgs)
2332
      {
2333
         resolveArgs();
2334
      }
2335
      referenceRecord = null;
2336
      buffer.reset();
2337
      
2338
      if (cursor != null)
2339
      {
2340
         cursor.reset();
2341
      }
2342
   }
2343
   
2344
   /**
2345
    * Get the number of tables joined by this query.
2346
    *
2347
    * @return  Always 1.
2348
    */
2349
   public int getTableCount()
2350
   {
2351
      return 1;
2352
   }
2353
   
2354
   /**
2355
    * Get all the off-end listeners associated with this query.
2356
    * 
2357
    * @return  the off-end listeners associated with this query.
2358
    */
2359
   public List<QueryOffEndListener> getOffEndListeners()
2360
   {
2361
      List<QueryOffEndListener> listeners = new ArrayList<>(1);
2362
      listeners.add(buffer.getQueryOffEndListener());
2363
      
2364
      return listeners;
2365
   }
2366
   
2367
   /**
2368
    * Load a record into its associated buffer, given an array of size one,
2369
    * containing a primary key ID value of the record to be loaded, or the DMO
2370
    * itself.
2371
    * <p>
2372
    * In the event an expected value cannot be loaded (e.g., the record has
2373
    * been deleted or is otherwise no longer available), this method throws
2374
    * {@link MissingRecordException}, after setting the associated buffer
2375
    * into {@link RecordBuffer#setUnknownMode() unknown mode}. Note that a
2376
    * record being locked by another session does not constitute a "missing"
2377
    * record.
2378
    * 
2379
    * @param   data
2380
    *          Array containing a single primary key ID or DMO.
2381
    * @param   lockType
2382
    *          Lock type which should by used.  Set to <code>null</code> to
2383
    *          allow query to use its current lock type.
2384
    * @param   silentIfNullId
2385
    *          If <code>true</code>, do not raise {@link MissingRecordException}
2386
    *          if some of the provided IDs are <code>null</code>. <code>null</code>
2387
    *          IDs are valid for queries with OUTER join.
2388
    * 
2389
    * @throws  MissingRecordException
2390
    *          if any record cannot be loaded, because it is no longer available
2391
    *          (e.g., deleted, etc.).
2392
    * @throws  PersistenceException
2393
    *          if there is an error loading data.
2394
    *
2395
    * @see     #getRow()
2396
    */
2397
   public void load(Object[] data, LockType lockType, boolean silentIfNullId)
2398
   throws PersistenceException
2399
   {
2400
      try
2401
      {
2402
         LockType lt = (lockType != null ? lockType : this.lockType);
2403
         verifyScrolling();
2404
         Long[] ids = new Long[1];
2405
         Object datum = data[0];
2406
         ids[0] = datum instanceof Record ? ((Record) datum).primaryKey() : (Long) datum;
2407
         
2408
         if (cursor.reposition(ids, false))
2409
         {
2410
            cursor.next();
2411
         }
2412
         
2413
         Record dmo = loadByValue(data, lt, silentIfNullId);
2414
         OffEnd offEnd = (dmo == null ? OffEnd.BACK : OffEnd.NONE);
2415
         updateBuffer(dmo, lt, silentIfNullId, offEnd);
2416
         
2417
         if (dmo == null)
2418
         {
2419
            buffer.setUnknownMode();
2420
            
2421
            if (!(datum == null && silentIfNullId))
2422
            {
2423
               String table = buffer.getTable();
2424
               RecordIdentifier ident = new RecordIdentifier(table, ids[0]);
2425
               
2426
               throw new MissingRecordException(ident);
2427
            }
2428
         }
2429
      }
2430
      finally
2431
      {
2432
         // Reset dirty flag.
2433
         dirtyCopy = false;
2434
      }
2435
   }
2436
   
2437
   /**
2438
    * Set the given results object into this query.  Only works if cursor is not null.
2439
    *
2440
    * @param   results
2441
    *          New result set for this query.
2442
    */
2443
   public void setResults(Results results)
2444
   {
2445
      if (cursor != null)
2446
      {
2447
         cursor.reset();
2448
         results.reset();
2449
         while (results.next())
2450
         {
2451
            cursor.addResultNext(results.get());
2452
         }
2453
         results.reset();
2454
      }
2455
   }
2456
   
2457
   /**
2458
    * Assemble an array of size one and store in it the primary key ID for
2459
    * the current record in the buffer underlying this query.  This method
2460
    * essentially takes a lightweight snapshot of the currently loaded
2461
    * record, so that this may be restored later.
2462
    *
2463
    * @return  Array containing a single primary key ID.
2464
    *
2465
    * @see     #load
2466
    */
2467
   public Object[] getRow()
2468
   {
2469
      Record dmo = referenceRecord != null ? referenceRecord : buffer.getSnapshot();
2470
      if (dmo == null && !buffer.isUnknownMode())
2471
      {
2472
         throw new NullPointerException("No reference record available");
2473
      }
2474
      
2475
      return (new Object[] { dmo });
2476
   }
2477
   
2478
   /**
2479
    * Fetch the array of primary key IDs associated with the first result in
2480
    * this query's result list.
2481
    * <p>
2482
    * This implementation always returns an array with a single element,
2483
    * (or <code>null</code>), since this query type uses a single record
2484
    * buffer.
2485
    *
2486
    * @return  Array of record IDs reflecting the first query result, or
2487
    *          <code>null</code> if there is no such result.
2488
    */
2489
   public Object[] peekFirst()
2490
   {
2491
      if (!prepareFetch())
2492
      {
2493
         return null;
2494
      }
2495
      
2496
      buffer.release(false);
2497
      
2498
      FastFindKey ffk = null;
2499
      if (referenceRecord == null)
2500
      {
2501
         if (ffEnable)
2502
         {
2503
            ffk = new FastFindKey(FIRST, index, null);
2504
            referenceRecord = ffCache.get(ffk);
2505
            if (referenceRecord != null)
2506
            {
2507
               ffk = null; // prevent putting it back
2508
            }
2509
         }
2510
      }
2511
      
2512
      if (referenceRecord == null)
2513
      {
2514
         activateFirst();
2515
         referenceRecord = execute(getCurrentArgs(), LockType.NONE, false, false);
2516
      }
2517
      
2518
      // save the ff result, if the case
2519
      if (ffk != null && referenceRecord != null)
2520
      {
2521
         ffCache.put(ffk, referenceRecord);
2522
      }
2523
      
2524
      if (referenceRecord == null)
2525
      {
2526
         return null;
2527
      }
2528
      
2529
      return new Object[] { referenceRecord };
2530
   }
2531
   
2532
   /**
2533
    * Fetch the array of primary key IDs associated with the last result in
2534
    * this query's result list.
2535
    * <p>
2536
    * This implementation always returns an array with a single element,
2537
    * (or <code>null</code>), since this query type uses a single record
2538
    * buffer.
2539
    *
2540
    * @return  Array of record IDs reflecting the last query result, or
2541
    *          <code>null</code> if there is no such result.
2542
    */
2543
   public Object[] peekLast()
2544
   {
2545
      if (!prepareFetch())
2546
      {
2547
         return null;
2548
      }
2549
      
2550
      buffer.release(false);
2551
   
2552
      FastFindKey ffk = null;
2553
      if (referenceRecord == null)
2554
      {
2555
         if (ffEnable)
2556
         {
2557
            ffk = new FastFindKey(LAST, index, null);
2558
            referenceRecord = ffCache.get(ffk);
2559
            if (referenceRecord != null)
2560
            {
2561
               ffk = null; // prevent putting it back
2562
            }
2563
         }
2564
      }
2565
      
2566
      if (referenceRecord == null)
2567
      {
2568
         activateLast();
2569
         referenceRecord = execute(getCurrentArgs(), LockType.NONE, false, false);
2570
      }
2571
      
2572
      // save the ff result, if the case
2573
      if (ffk != null && referenceRecord != null)
2574
      {
2575
         ffCache.put(ffk, referenceRecord);
2576
      }
2577
      
2578
      if (referenceRecord == null)
2579
      {
2580
         return null;
2581
      }
2582
      
2583
      return new Object[] { referenceRecord };
2584
   }
2585
   
2586
   /**
2587
    * Fetch the array of primary key IDs associated with the next result in
2588
    * this query's result list.
2589
    * <p>
2590
    * This implementation always returns an array with a single element,
2591
    * (or <code>null</code>), since this query type uses a single record
2592
    * buffer.
2593
    *
2594
    * @return  Array of record IDs reflecting the next query result, or
2595
    *          <code>null</code> if there is no such result.
2596
    */
2597
   public Object[] peekNext()
2598
   {
2599
      if (!prepareFetch())
2600
      {
2601
         return null;
2602
      }
2603
      
2604
      buffer.release(false);
2605
      referenceRecord = findNext(getCurrentArgs(), LockType.NONE, false);
2606
      if (referenceRecord == null)
2607
      {
2608
         return null;
2609
      }
2610
      
2611
      return (new Object[] { referenceRecord });
2612
   }
2613
   
2614
   /**
2615
    * Fetch the array of primary key IDs associated with the previous result
2616
    * in this query's result list.
2617
    * <p>
2618
    * This implementation always returns an array with a single element,
2619
    * (or <code>null</code>), since this query type uses a single record
2620
    * buffer.
2621
    *
2622
    * @return  Array of record IDs reflecting the previous query result, or
2623
    *          <code>null</code> if there is no such result.
2624
    */
2625
   public Object[] peekPrevious()
2626
   {
2627
      if (!prepareFetch())
2628
      {
2629
         return null;
2630
      }
2631
      
2632
      buffer.release(false);
2633
      referenceRecord = findPrevious(getCurrentArgs(), LockType.NONE, false);
2634
      if (referenceRecord == null)
2635
      {
2636
         return null;
2637
      }
2638
      
2639
      return (new Object[] { referenceRecord });
2640
   }
2641
   
2642
   /**
2643
    * Get an array of all record buffers managed by this query.
2644
    *
2645
    * @return  All record buffers managed by this query.
2646
    */
2647
   @Override
2648
   public RecordBuffer[] getRecordBuffers()
2649
   {
2650
      return (new RecordBuffer[] { buffer });
2651
   }
2652
   
2653
   /**
2654
    * Get the query substitution arguments associated with this query, if any.
2655
    * 
2656
    * @return  Substitution arguments, in order, or <code>null</code> if there are none.
2657
    */
2658
   @Override
2659
   public Object[] getArgs()
2660
   {
2661
      return dfArgs;
2662
   }
2663
   
2664
   /**
2665
    * Get the legacy natural join, if any, associated with this query.
2666
    * 
2667
    * @return  Legacy join object, or <code>null</code> if none.
2668
    */
2669
   @Override
2670
   public AbstractJoin getJoin()
2671
   {
2672
      return join;
2673
   }
2674
   
2675
   /**
2676
    * Get the original, HQL where clause associated with this query.
2677
    * 
2678
    * @return  Original where clause, or <code>null</code> if there is none.
2679
    */
2680
   @Override
2681
   public String getOriginalWhere()
2682
   {
2683
      return dfWhere;
2684
   }
2685
   
2686
   /**
2687
    * Force this query to operate in dynamic retrieval mode, if it supports this mode.
2688
    * <p>
2689
    * This implementation does nothing, since this query type always is in dynamic retrieval
2690
    * mode.
2691
    */
2692
   @Override
2693
   public void forceDynamicOperation()
2694
   {
2695
      // no-op
2696
   }
2697
   
2698
   /**
2699
    * Get the external buffers, if any, associated with this query.
2700
    *
2701
    * @return  Array of external buffers or {@code null}.
2702
    */
2703
   @Override
2704
   public RecordBuffer[] getExternalBuffers()
2705
   {
2706
      return super.getExternalBuffers();
2707
   }
2708
   
2709
   /**
2710
    * Get the sort phrase associated with this query.
2711
    * 
2712
    * @return  Sort phrase.
2713
    */
2714
   public String getSortPhrase()
2715
   {
2716
      return sort;
2717
   }
2718
   
2719
   /**
2720
    * Create an adaptive query component based on the information in this query (which is
2721
    * presumed to be a single-table query), and given a list of adaptive query components which
2722
    * represent outer, nested query loops, which will perform a server-side join to the
2723
    * innermost, nested query component.
2724
    * 
2725
    * @param   joinList
2726
    *          List of adaptive components representing nested query loops which will contain
2727
    *          the adaptive query component returned by this method. The last component in the
2728
    *          list represents the nested query loop immediately containing the returned query
2729
    *          component, with which the join will be made.
2730
    * @param   query
2731
    *          Adaptive query which will manage the server-side join of <code>joinList</code> and
2732
    *          the returned query component.
2733
    * @param   iteration
2734
    *          Iteration type: FIRST, LAST, or NEXT.
2735
    * @param   outer
2736
    *          <code>true</code> if the join should be a left outer join. This type of join is
2737
    *          not supported at the time of this writing, so this parameter should always be
2738
    *          <code>false</code>. It is here to support a planned, future enhancement.
2739
    * @param   fallback
2740
    *          A compound query component to use in the event the multi-table adaptive query
2741
    *          switches from preselect to dynamic retrieval mode.
2742
    * 
2743
    * @return  An adaptive query component suitable for a server-side join, based on the
2744
    *          information in this query.
2745
    */
2746
   @Override
2747
   public AdaptiveComponent makeAdaptiveServerJoinComponent(List<AdaptiveComponent> joinList,
2748
                                                            AdaptiveQuery query,
2749
                                                            int iteration,
2750
                                                            boolean outer,
2751
                                                            CompoundComponent fallback)
2752
   {
2753
      // NOTE: dynamic components must be already processed (dfWhere and dfArgs configured) 
2754
      QueryComponent.ServerJoinData sjd =
2755
            QueryComponent.prepareServerJoinData(joinList, buffer, join, dfArgs);
2756
      BufferReference inverse = (join != null ? join.getInverse() : null);
2757
      AdaptiveComponent comp = new AdaptiveComponent(query,
2758
                                                     buffer,
2759
                                                     sjd.join,
2760
                                                     dfWhere,
2761
                                                     sort,
2762
                                                     indexInfo,
2763
                                                     lockType,
2764
                                                     sjd.args,
2765
                                                     iteration,
2766
                                                     outer,
2767
                                                     inverse);
2768
      comp.setReferenceSubs(sjd.referenceSubs);
2769
      
2770
      // gather all related buffers, including [relatedBuffers] from [sjd] and eventual external
2771
      // buffers from original query component
2772
      Set<RecordBuffer> relatedBuffers = new HashSet<>();
2773
      if (sjd.relatedBuffers != null)
2774
      {
2775
         relatedBuffers.addAll(sjd.relatedBuffers);
2776
      }
2777
      RecordBuffer[] externalBuffers = fallback.getQuery().getExternalBuffers();
2778
      if (externalBuffers != null && externalBuffers.length != 0)
2779
      {
2780
         relatedBuffers.addAll(Arrays.asList(externalBuffers));
2781
      }
2782
      if (!relatedBuffers.isEmpty())
2783
      {
2784
         comp.setRelatedBuffers(relatedBuffers);
2785
      }
2786
      
2787
      comp.setFallback(fallback);
2788
      if (!joinList.isEmpty())
2789
      {
2790
         comp.setNotTop();
2791
      }
2792
      
2793
      if (iteration == FIRST || iteration == LAST)
2794
      {
2795
         try
2796
         {
2797
            comp.setIteration(iteration, sort);
2798
         }
2799
         catch (PersistenceException exc)
2800
         {
2801
            // should only happen if we have a runtime programming error or conversion error
2802
            // (or with bad, hand-written business logic)
2803
            throw new IllegalArgumentException(exc);
2804
         }
2805
      }
2806
      
2807
      return comp;
2808
   }
2809
   
2810
   /**
2811
    * Create a preselect query component based on the information in this query (which is
2812
    * presumed to be a single-table query), and given a list of preselect query components which
2813
    * represent outer, nested query loops, which will perform a server-side join to the
2814
    * innermost, nested query component.
2815
    * 
2816
    * @param   joinList
2817
    *          List of preselect components representing nested query loops which will contain
2818
    *          the preselect query component returned by this method. The last component in the
2819
    *          list represents the nested query loop immediately containing the returned query
2820
    *          component, with which the join will be made.
2821
    * @param   iteration
2822
    *          Iteration type: FIRST, LAST, or NEXT.
2823
    * @param   outer
2824
    *          <code>true</code> if the join should be a left outer join. This type of join is
2825
    *          not supported at the time of this writing, so this parameter should always be
2826
    *          <code>false</code>. It is here to support a planned, future enhancement.
2827
    * 
2828
    * @return  A preselect query component suitable for a server-side join, based on the
2829
    *          information in this query.
2830
    */
2831
   @Override
2832
   public QueryComponent makePreselectServerJoinComponent(List<QueryComponent> joinList,
2833
                                                          int iteration,
2834
                                                          boolean outer)
2835
   {
2836
      // NOTE: dynamic components must be already processed (dfWhere and dfArgs configured)
2837
      QueryComponent.ServerJoinData sjd =
2838
            QueryComponent.prepareServerJoinData(joinList, buffer, join, dfArgs);
2839
      QueryComponent comp = new QueryComponent(this,
2840
                                               buffer,
2841
                                               sjd.join,
2842
                                               dfWhere,
2843
                                               indexInfo,
2844
                                               lockType,
2845
                                               sjd.args,
2846
                                               iteration,
2847
                                               outer);
2848
      comp.setReferenceSubs(sjd.referenceSubs);
2849
      comp.setRelatedBuffers(sjd.relatedBuffers);
2850
      
2851
      if (iteration == FIRST || iteration == LAST)
2852
      {
2853
         try
2854
         {
2855
            comp.setIteration(iteration, sort);
2856
         }
2857
         catch (PersistenceException exc)
2858
         {
2859
            // should only happen if we have a runtime programming error or conversion error
2860
            // (or with bad, hand-written business logic)
2861
            throw new IllegalArgumentException(exc);
2862
         }
2863
      }
2864
      
2865
      return comp;
2866
   }
2867
   
2868
   /**
2869
    * Indicate whether this query has a client-side where clause expression associated with it.
2870
    * 
2871
    * @return  <code>true</code> if there is a where expression; else <code>false</code>.
2872
    */
2873
   @Override
2874
   public boolean hasWhereExpression()
2875
   {
2876
      return (whereExpr != null);
2877
   }
2878
   
2879
   /**
2880
    * Report the record buffer for whose changes this object is interested
2881
    * in listening.  This listener will be registered with the {@link
2882
    * ChangeBroker} to receive notifications of any change to DMOs whose
2883
    * interface types match that returned by the {@link
2884
    * RecordBuffer#getDMOInterface} method of the returned buffer.
2885
    *
2886
    * @return  An iterator on a single object:  the record buffer backing this query.
2887
    */
2888
   public Iterator<RecordBuffer> recordBuffers()
2889
   {
2890
      Iterator<RecordBuffer> iter = new Iterator<RecordBuffer>()
2891
      {
2892
         private boolean avail = true;
2893
         public boolean hasNext() { return avail; }
2894
         public RecordBuffer next() { avail = false;  return buffer; }
2895
         public void remove() { throw new UnsupportedOperationException(); }
2896
      };
2897
      
2898
      return iter;
2899
   }
2900
   
2901
   /**
2902
    * Register all <code>RecordChangeListener</code>s associated with this
2903
    * query (if any), with the context-local <code>ChangeBroker</code> at the
2904
    * indicated scope.
2905
    * 
2906
    * @param   scope
2907
    *          Scope at which listeners should be registered with the change broker.
2908
    */
2909
   public void registerRecordChangeListeners(int scope)
2910
   {
2911
      ChangeBroker broker = ChangeBroker.get();
2912
      broker.addListener(this, scope);
2913
      if (placeholderCleaner != null)
2914
      {
2915
         broker.addListener(placeholderCleaner, scope);
2916
      }
2917
      
2918
      // if these were added as global listeners in ChangeBroker remember to remove them
2919
      // when the query is destroyed. Otherwise, the ChangeBroker will drop them automatically
2920
      // when the respective scope is finished.
2921
      if (scope == 0)
2922
      {
2923
         unregisterOnCleanup = true;
2924
      }
2925
   }
2926
   
2927
   /**
2928
    * Explicitly close the prepared query.
2929
    */
2930
   @Override
2931
   public void close()
2932
   {
2933
      close(false, false);
2934
   }
2935
   
2936
   /**
2937
    * Explicitly close the prepared query.
2938
    * 
2939
    * @param    inResource
2940
    *           Flag indicating this query is used in a QUERY legacy resource.
2941
    * @param    dynamic
2942
    *           Flag indicating this is part of a dynamic query.
2943
    */
2944
   @Override
2945
   public void close(boolean inResource, boolean dynamic)
2946
   {
2947
      if (inResource && buffer == null)
2948
      {
2949
         // nothing to close, the query was not initialized yet.
2950
         // This happens only for dynamic queries, when the query was explicitly closed or
2951
         // re-prepared before having the chance to be fully initialized with query-open
2952
         return;
2953
      }
2954
      if (inResource && !unregisterOnCleanup)
2955
      {
2956
         // remove it explicitly
2957
         ChangeBroker broker = ChangeBroker.get();
2958
         broker.removeListener(this);
2959
      }
2960
      
2961
      super.close();
2962
   }
2963
   
2964
   /**
2965
    * The cleanup method is overridden to allow the listener(s) to be removed from the
2966
    * {@code ChangeBroker} if they were added in the global scope, otherwise, the 
2967
    * {@code ChangeBroker} will drop them automatically when the respective scope is finished.
2968
    */
2969
   @Override
2970
   public void cleanup()
2971
   {
2972
      if (unregisterOnCleanup)
2973
      {
2974
         ChangeBroker broker = ChangeBroker.get();
2975
         broker.removeFromGlobal(this);
2976
         if (placeholderCleaner != null)
2977
         {
2978
            broker.removeFromGlobal(placeholderCleaner);
2979
         }
2980
         
2981
         unregisterOnCleanup = false;
2982
      }
2983
      
2984
      // let the super class do the rest of the cleanup
2985
      super.cleanup();
2986
   }
2987
   
2988
   /**
2989
    * Respond to a record change event.
2990
    *
2991
    * @param   event
2992
    *          Event which describes the DMO state change.
2993
    *
2994
    * @throws  PersistenceException
2995
    *          if any error occurs while processing the state change event.
2996
    */
2997
   public void stateChanged(RecordChangeEvent event)
2998
   throws PersistenceException
2999
   {
3000
      if (cursor == null)
3001
      {
3002
         return;
3003
      }
3004
      
3005
      if (event.isInsert())
3006
      {
3007
         cursor.reset();
3008
         
3009
         return;
3010
      }
3011
      
3012
      if (event.isDelete())
3013
      {
3014
         int idx = -1;
3015
         RecordBuffer source = event.getSource();
3016
         Iterator<RecordBuffer> iter = recordBuffers();
3017
         for (int i = 0; iter.hasNext() && idx < 0; i++)
3018
         {
3019
            if (source == iter.next())
3020
            {
3021
               idx = i;
3022
            }
3023
         }
3024
         
3025
         // Reset the cache if it contains a reference to the deleted DMO.
3026
         if (idx >= 0 && cursor.contains(event.getDMO().primaryKey(), idx))
3027
         {
3028
            cursor.reset();
3029
         }
3030
      }
3031
   }
3032
   
3033
   /**
3034
    * Get the off-end status of this query, which indicates whether the query has run off either
3035
    * end of its results.
3036
    * 
3037
    * @return  One of the {@link OffEnd} constants {@code NONE}, {@code FRONT}, or {@code BACK}.
3038
    */
3039
   public OffEnd getOffEnd()
3040
   {
3041
      if (helper == null)
3042
      {
3043
         return OffEnd.NONE;
3044
      }
3045
      
3046
      SortIndex si = helper.getSortIndex();
3047
      
3048
      return buffer.getOffEnd(si, inverseSorting);
3049
   }
3050
   
3051
   /**
3052
    * Conversion of INDEX-INFORMATION attribute (KW_IDX_INFO).
3053
    *
3054
    * Getter of INDEX-INFORMATION attribute
3055
    *
3056
    * @return A character string consisting of a comma-separated list of the index or
3057
    *         indexes the query uses at the level of join specified.
3058
    */
3059
   @LegacyAttribute(name = "INDEX-INFORMATION")
3060
   public character indexInformation(NumberType n)
3061
   {
3062
      return new character(indexInfo);
3063
   }
3064
   
3065
   /**
3066
    * Set the record whose data defines current "place" in query results.
3067
    *
3068
    * @param referenceRecord
3069
    *        The record whose data defines current "place" in query results.
3070
    */
3071
   void setReferenceRecord(Record referenceRecord)
3072
   {
3073
      this.referenceRecord = referenceRecord;
3074
   }
3075
   
3076
   /**
3077
    * Register the RecordChangeListener instance with the ChangeBroker.
3078
    */
3079
   protected void registerChangeListener()
3080
   {
3081
      // Listen for changes to our reference record.  If it is modified, we
3082
      // can no longer rely upon it as an accurate placeholder.
3083
      placeholderCleaner = new RecordChangeListener()
3084
      {
3085
         public Iterator<RecordBuffer> recordBuffers()
3086
         {
3087
            return Collections.singletonList(buffer).iterator();
3088
         }
3089
         public void stateChanged(RecordChangeEvent event)
3090
         throws PersistenceException
3091
         {
3092
            if (event.getDMO() == referenceRecord &&
3093
                event.isUpdate()                  &&
3094
                !event.isExtentFieldChangeOnly())
3095
            {
3096
               // Rely on buffer snapshot as placeholder instead.
3097
               referenceRecord = null;
3098
            }
3099
         }
3100
      };
3101
      ChangeBroker.get().addListener(placeholderCleaner);
3102
   }
3103
   
3104
   /**
3105
    * Indicate whether this query should be a projection query, retrieving only the primary key
3106
    * of the target record, or whether it should retrieve the entire record as early as possible.
3107
    * 
3108
    * @return  <code>true</code> for a projection query, else <code>false</code>.
3109
    */
3110
   protected boolean isIdOnly()
3111
   {
3112
      // Generally, we want to execute as few queries as possible to retrieve a record. The
3113
      // minimum is one, whereby we get all the columns in one pass. However, we have to
3114
      // consider that for NEXT and PREVIOUS requests, we may execute a query per sort component
3115
      // (plus 1 for the primary key if the sort clause does not represent a unique index).
3116
      // In that case, we want to execute a projection query (i.e., get only the primary key).
3117
      // Also we can only retrieve all the columns in the first pass for a no-lock query,
3118
      // because in order to ensure integrity of a locked record, we have to apply the lock
3119
      // before retrieving the full data. Finally, honor the fullRecords request if possible.
3120
      switch (activeBundleKey)
3121
      {
3122
         // TODO: if a next/previous sort clause contains only 1 component, we could be less
3123
         // strict about forcing a projection query here; however, that would require making
3124
         // a separate call to SortCriterion.parse(String, RecordBuffer), which is somewhat
3125
         // expensive, and we are about to do it momentarily in HQLHelper
3126
         case NEXT:
3127
         case PREVIOUS:
3128
            return true;
3129
         default:
3130
            return !(fullRecords || LockType.NONE.equals(lockType) || buffer.isTemporary());
3131
//            return !(fullRecords || buffer.isTemporary());
3132
//            return !buffer.isTemporary();
3133
//            return true;
3134
//            return !fullRecords;
3135
      }
3136
   }
3137
   
3138
   /**
3139
    * Release the current record, if any, from the buffer which is used by
3140
    * this query.
3141
    *
3142
    * @throws  QueryOffEndException
3143
    *          if the buffer contained a record and it was released.
3144
    */
3145
   public void releaseBuffers()
3146
   {
3147
      // this method is called in the context of this query being a component in an iterating
3148
      // query, so do not allow write trigger to fire
3149
      buffer.release(false);
3150
   }
3151
   
3152
   /**
3153
    * Adds a new constraint term to the where predicate of this query, effectively filtering out
3154
    * any rows whose referenced field does not match the requested value. Semantically, the query
3155
    * will select rows that match
3156
    * <p>
3157
    * {@code new-where := (fr = val) AND (old-query)}.
3158
    * <p>
3159
    * Note: The constraints are exclusive meaning that if constraints will be added for same field of same
3160
    *       buffer, the query will return an empty result because the field cannot be at the same time equals
3161
    *       to two different values.
3162
    * <p>
3163
    * Use {@link #clearDynamicFilters} to clean all dynamically added criteria and restore the
3164
    * sorting order of the query to its original form.
3165
    *
3166
    * @param   fr
3167
    *          A reference to field used as filtering criterion. Only the rows that matches the
3168
    *          specified value for this field will be selected. Must not be {@code null}.
3169
    * @param   val
3170
    *          The filtering value for this constraint. Can be {@code null} or {@code unknown}, in
3171
    *          which case the SQL {@code null} value will be assumed. In this case the predicate
3172
    *          will look like:
3173
    *           <p>
3174
    *          {@code new-where := (fr IS  NULL) AND (old-query)}
3175
    * @param   format
3176
    *          The format to be used when comparing values. The reason for this parameter is that
3177
    *          for some datatype (ex: {@code decimal}) the on-screen value can be different from
3178
    *          the database (because of rounding).
3179
    *
3180
    * @return  {@code true} if the filtering constraint was successfully added. If the field
3181
    *          reference is not related to the buffer(s) of this query, {@code false} is returned.
3182
    */
3183
   @Override
3184
   public boolean addDynamicFilter(FieldReference fr, BaseDataType val, String format)
3185
   {
3186
      if (fr.getParentBuffer() != getBuffer())
3187
      {
3188
         return false; // not my buffer
3189
      }
3190
      
3191
      if (dynamicFilters == null)
3192
      {
3193
         dynamicFilters = new LinkedHashMap<>(5);
3194
         dynamicFormats = new HashMap<>(5);
3195
      }
3196
      dynamicFilters.remove(fr);
3197
      dynamicFilters.put(fr, val);
3198
      dynamicFormats.put(fr, format);
3199
      
3200
      StringBuilder sb = new StringBuilder();
3201
      List<Object> dArgs = new ArrayList<>(dynamicFilters.size());
3202
      Iterator<Map.Entry<FieldReference, BaseDataType>> dynamicFilters =
3203
            this.dynamicFilters.entrySet().iterator();
3204
      while (dynamicFilters.hasNext())
3205
      {
3206
         Map.Entry<FieldReference, BaseDataType> filter = dynamicFilters.next();
3207
         BaseDataType bdtVal = filter.getValue();
3208
         String fieldName = filter.getKey().toString();
3209
         boolean isNull = (bdtVal == null) || bdtVal.isUnknown();
3210
         boolean isCharType = bdtVal instanceof Text;
3211
         sb.append("(");
3212
         
3213
         if (isNull)
3214
         {
3215
            sb.append(fieldName).append(" is null");
3216
         }
3217
         else
3218
         {
3219
            if (isCharType)
3220
            {
3221
               // the user will not know the case-sensitivity of the database fields, so it is
3222
               // best to make the filter for text fields always be case-insensitive. This has the
3223
               // greatest likelihood of using an index (most fields are case-insensitive), and it
3224
               // is the most user-friendly.
3225
               sb.append("upper(trim(").append(fieldName).append("))").append(" like ?");
3226
               String pattern = TextOps.trim(TextOps.toUpperCase((Text) bdtVal)).getValue();
3227
               bdtVal = new character("%" + pattern + "%");
3228
            }
3229
            else if (bdtVal instanceof decimal)
3230
            {
3231
               // because of rounding, the displayed value may not be equals to the one from
3232
               // database. To make sure we filter the right rows, we will use the format matching
3233
               // the one used by shown data
3234
               String fmt = dynamicFormats.get(filter.getKey());
3235
               if (fmt != null)
3236
               {
3237
                  sb.append("toString(").append(fieldName).append(",'").append(fmt).append("')= ?");
3238
                  bdtVal = new character(bdtVal.toString(fmt));
3239
               }
3240
               else
3241
               {
3242
                  // format not provided, we cannot use it, compare directly instead
3243
                  sb.append(fieldName).append(" = ?");
3244
               }
3245
            }
3246
            else
3247
            {
3248
               sb.append(fieldName).append(" = ?");
3249
            }
3250
            dArgs.add(filter.getValue()); // TODO: dArgs.add(bdtVal) instead?
3251
         }
3252
         
3253
         if (dynamicFilters.hasNext() || where != null)
3254
         {
3255
            sb.append(") and ");
3256
         }
3257
         else
3258
         {
3259
            sb.append(")");
3260
         }
3261
      }
3262
      
3263
      if (where != null)
3264
      {
3265
         sb.append("(").append(where).append(")");
3266
      }
3267
      dfWhere = sb.toString();
3268
      
3269
      int dArgCount = dArgs.size();
3270
      if (args != null && dArgCount != 0)
3271
      {
3272
         dfArgs = new Object[args.length + dArgCount];
3273
         System.arraycopy(dArgs.toArray(), 0, dfArgs, 0, dArgCount);
3274
         System.arraycopy(args, 0, dfArgs, dArgCount, args.length);
3275
      }
3276
      else if (dArgCount != 0)
3277
      {
3278
         dfArgs = new Object[dArgCount];
3279
         dArgs.toArray(dfArgs);
3280
      }
3281
      else
3282
      {
3283
         dfArgs = null;
3284
      }
3285
      
3286
      helper = null;
3287
      nonNullArgsHelper = null;
3288
      
3289
      return true;
3290
   }
3291
   
3292
   /**
3293
    * Clears any dynamically filters added at runtime. The query predicate will be restored to its
3294
    * form from generated at conversion time.
3295
    */
3296
   @Override
3297
   public void clearDynamicFilters()
3298
   {
3299
      if (dynamicFilters == null)
3300
      {
3301
         return;
3302
      }
3303
      
3304
      // restore the where predicate and parameters:
3305
      dfWhere = where;
3306
      dfArgs = args;
3307
      
3308
      // cleanup and prepare for re-initialization
3309
      dynamicFilters = null;
3310
      dynamicFormats = null;
3311
      helper = null;
3312
      nonNullArgsHelper = null;
3313
      dynamicFilterArgs = null;
3314
   }
3315
   
3316
   /**
3317
    * Prepare buffer associated with this query for the query's execution.
3318
    * This resets certain per-query state tracked by the record buffer.
3319
    * 
3320
    * @throws  ValidationException
3321
    *          if the parent's implementation is invoked and a buffer flush
3322
    *          triggers a validation error for the record currently stored in
3323
    *          the buffer.
3324
    */
3325
   protected void prepareBuffer()
3326
   throws ValidationException
3327
   {
3328
      buffer.flush();
3329
      buffer.setLocked(false);
3330
   }
3331
   
3332
   /**
3333
    * Update the record buffer with the given DMO, applying the specified
3334
    * lock type and error-if-null setting.  This implementation causes the
3335
    * record buffer's internal placeholder snapshot to be reset if
3336
    * <code>dmo</code> is null.
3337
    *
3338
    * @param   dmo
3339
    *          Record to be stored in buffer.
3340
    * @param   lockType
3341
    *          Lock type associated with the record, which will be remembered
3342
    *          by the buffer.
3343
    * @param   errorIfNull
3344
    *          If <code>true</code>, setting a <code>null</code> value as the
3345
    *          current record in an underlying buffer will raise an error
3346
    *          condition (if not in silent error mode); if <code>false</code>,
3347
    *          this action will raise an end condition instead.
3348
    * @param   offEnd
3349
    *          Enum indicating whether query is off-end, and if so, in which
3350
    *          direction.
3351
    */
3352
   protected void updateBuffer(Record dmo,
3353
                               LockType lockType,
3354
                               boolean errorIfNull,
3355
                               OffEnd offEnd)
3356
   {
3357
      SortIndex sortIndex = getHelper().getSortIndex();
3358
      buffer.setRecord(dmo,
3359
                       lockType,
3360
                       errorIfNull,
3361
                       sortIndex,
3362
                       offEnd,
3363
                       inverseSorting,
3364
                       dirtyCopy,
3365
                       true);
3366
   }
3367
   
3368
   /**
3369
    * Return an object which represents a property value which defines the
3370
    * end of a <i>sort band</i>.  A <i>sort band</i> is a series of one or
3371
    * more records which share the same value for a particular property,
3372
    * where that property is used as the coarsest sorting criterion defined
3373
    * by this query's <code>order by</code> clause.
3374
    * <p>
3375
    * If the break value for this query currently is non-<code>null</code>,
3376
    * this indicates that the most recently retrieved record has crossed a
3377
    * <i>sort band</i> boundary, and that a new band has begun.  This value
3378
    * will only be non-<code>null</code> at the point at which such a record
3379
    * is found, and will be <code>null</code> for all other records.
3380
    * <p>
3381
    * Note:  break values are only tracked for invocations of
3382
    * <code>next</code> and <code>previous</code>.  Other retrievals will
3383
    * results in break value being <code>null</code>.
3384
    *
3385
    * @return  Break value if dynamic query crossed a sort band boundary,
3386
    *          else <code>null</code>.
3387
    */
3388
   protected Object getBreakValue()
3389
   {
3390
      return breakValue;
3391
   }
3392
   
3393
   /**
3394
    * Get the record buffer associated with this query.
3395
    * 
3396
    * @return  Record buffer.
3397
    */
3398
   protected RecordBuffer getBuffer()
3399
   {
3400
      return buffer;
3401
   }
3402
   
3403
   /**
3404
    * Retrieve this query's arguments, as currently resolved.
3405
    * 
3406
    * @return  Current arguments.
3407
    */
3408
   protected Object[] getCurrentArgs()
3409
   {
3410
      return currentArgs;
3411
   }
3412
   
3413
   /**
3414
    * Retrieve this query's lock type.
3415
    * 
3416
    * @return  Lock type
3417
    */
3418
   protected LockType getLockType()
3419
   {
3420
      return lockType;
3421
   }
3422
   
3423
   /**
3424
    * Execute each query stored in the active HQL bundle in turn until we
3425
    * find a result or have no more statements to execute.  Each statement is
3426
    * decreasingly specific.
3427
    * <p>
3428
    * The latest record to be stored in the record buffer, if any, marks our
3429
    * current position in the query's results.  Its data is used to substitute
3430
    * into the placeholder parameters in the query statements' augmented where
3431
    * clauses.  These are added to the <code>values</code> provided as part of
3432
    * the base query.
3433
    * <p>
3434
    * <b>Client-Side Where Clause Handling</b><br>
3435
    * If this query involves a client-side where clause expression, an
3436
    * additional layer of criteria checking takes place.  In addition to the
3437
    * search described above, each returned result is temporarily stored in
3438
    * the associated record buffer, and the where expression is executed.
3439
    * If the expression indicates a match, the record is returned.  Otherwise,
3440
    * additional records are retrieved and tested until either a match is
3441
    * found or no more records are available in the requested navigation
3442
    * direction.
3443
    * <p>
3444
    * Unique queries combined with client-side where clauses require special
3445
    * handling and may perform particularly poorly as a result.  In this
3446
    * case, every record returned by the first-pass query must be tested
3447
    * against the client-side where expression, even once a result has been
3448
    * found.  This must be done to ensure <i>only one</i> record matches the
3449
    * specified criteria.  The only exception is the case where a second
3450
    * record is actually found, in which case the uniqueness requirement has
3451
    * been violated, and the remainder of the scan is aborted due to the
3452
    * error.
3453
    *
3454
    * @param   values
3455
    *          Array of substitution parameter values for the base where
3456
    *          clause (i.e., the un-augmented portion of the clause).
3457
    * @param   lockType
3458
    *          Type of lock to acquire for the found record.
3459
    * @param   unique
3460
    *          <code>true</code> if there should be no more than one match
3461
    *          for the conditions specified;  <code>false</code> if multiple
3462
    *          matches are possible.
3463
    * @param   updateLock
3464
    *          <code>true</code> if the status of the lock on the retrieved
3465
    *          record should be modified;  else <code>false</code>.  This is
3466
    *          set to <code>true</code> for actual retrievals, and to
3467
    *          <code>false</code> when only detecting whether a record would
3468
    *          be found, or when peeking a record.
3469
    *
3470
    * @return  The record resulting from the query, or <code>null</code> if
3471
    *          no record was found.
3472
    *
3473
    * @throws  ErrorConditionException
3474
    *          if a recoverable error occurred while executing.
3475
    */
3476
   protected Record execute(Object[] values,
3477
                            LockType lockType,
3478
                            boolean unique,
3479
                            boolean updateLock)
3480
   {
3481
      // if the JOINed buffer has no record, then we must return NULL
3482
      if (join != null)
3483
      {
3484
         BufferReference buffer = join.getInverse();
3485
         Record record = buffer.buffer().getCurrentRecord();
3486
         
3487
         if (record == null)
3488
         {
3489
            return null;
3490
         }
3491
      }
3492
      
3493
      if (whereExpr == null)
3494
      {
3495
         return executeImpl(values, lockType, unique, updateLock);
3496
      }
3497
      
3498
      Record dmo = null;
3499
      Record oldRefRec = referenceRecord;
3500
      HQLBundle bundle0 = activeBundle;
3501
      HQLBundle bundle1;
3502
      HQLBundle bundle2;
3503
      
3504
      // prepare the HQL bundles which will be used in the iterative, first pass scan
3505
      HQLHelper cachedHelper = getHelper();
3506
      switch (activeBundleKey)
3507
      {
3508
         case NEXT:
3509
            bundle1 = bundle2 = cachedHelper.next();
3510
            break;
3511
         case PREVIOUS:
3512
            bundle1 = bundle2 = cachedHelper.previous();
3513
            break;
3514
         case FIRST:
3515
         case UNIQUE:
3516
            bundle1 = cachedHelper.first();
3517
            bundle2 = cachedHelper.next();
3518
            break;
3519
         case LAST:
3520
            bundle1 = cachedHelper.last();
3521
            bundle2 = cachedHelper.previous();
3522
            break;
3523
         default:
3524
            throw new IllegalStateException("No active HQL bundle");
3525
      }
3526
      
3527
      Persistence persistence = buffer.getPersistence();
3528
      try
3529
      {
3530
         Record uniqueDMO = null;
3531
         buffer.pushTempContext();
3532
         
3533
         for (int i = 0; ; i++)
3534
         {
3535
            activeBundle = (i == 0 ? bundle1 : bundle2);
3536
            
3537
            // Find a candidate record, but do not lock it yet.
3538
            dmo = executeImpl(values, LockType.NONE, false, false);
3539
            if (dmo == null)
3540
            {
3541
               break;
3542
            }
3543
            referenceRecord = dmo;
3544
            
3545
            // Execute where expression.
3546
            buffer.setTempRecord(dmo);
3547
            if (whereExpr.get().booleanValue())
3548
            {
3549
               if (unique)
3550
               {
3551
                  if (uniqueDMO == null)
3552
                  {
3553
                     uniqueDMO = dmo;
3554
                  }
3555
                  else
3556
                  {
3557
                     persistence.uniqueResultViolation(buffer, null);
3558
                  }
3559
               }
3560
               else
3561
               {
3562
                  break;
3563
               }
3564
            }
3565
         }
3566
         
3567
         if (unique)
3568
         {
3569
            dmo = uniqueDMO;
3570
         }
3571
      }
3572
      catch (PersistenceException exc)
3573
      {
3574
         ErrorManager.recordOrThrowError(exc);
3575
         
3576
         return null;
3577
      }
3578
      finally
3579
      {
3580
         try
3581
         {
3582
            buffer.popTempContext();
3583
         }
3584
         catch (PersistenceException exc)
3585
         {
3586
            ErrorManager.recordOrThrowError(exc);
3587
         }
3588
         referenceRecord = oldRefRec;
3589
         activeBundle = bundle0;
3590
      }
3591
      
3592
      // If a record was found and successfully passed the client-side where
3593
      // expression filter, lock it now, if necessary.
3594
      if (dmo != null && !lockType.equals(LockType.NONE) && updateLock)
3595
      {
3596
         try
3597
         {
3598
            String table = buffer.getTable();
3599
            RecordIdentifier ident = new RecordIdentifier(table, dmo.primaryKey());
3600
            RecordLockContext lockCtx = buffer.getPersistenceContext().getRecordLockContext();
3601
            lockCtx.lock(ident, lockType);
3602
         }
3603
         catch (LockUnavailableException exc)
3604
         {
3605
            ErrorManager.recordOrThrowError(445, buffer.getLegacyName() + " record is locked", exc);
3606
            
3607
            // Cannot return record if it could not be locked.
3608
            dmo = null;
3609
         }
3610
      }
3611
      
3612
      return dmo;
3613
   }
3614
   
3615
   /**
3616
    * This method is invoked when a locking exception is caught during query
3617
    * execution.  This implementation delegates handling of the error to the
3618
    * <code>ErrorManager</code>.
3619
    * <p>
3620
    * Subclasses which require different handling must override this method.
3621
    * 
3622
    * @param   exc
3623
    *          Cause exception.
3624
    */
3625
   protected void handleExecuteException(LockUnavailableException exc)
3626
   {
3627
      buffer.setLocked(true);
3628
      ErrorManager.recordOrThrowError(445, buffer.getLegacyName() + " record is locked", exc);
3629
   }
3630
   
3631
   /**
3632
    * This method is invoked when a validation exception is caught during
3633
    * query execution.  This implementation delegates handling of the error
3634
    * to the <code>ErrorManager</code>.
3635
    * <p>
3636
    * Subclasses which require different handling must override this method.
3637
    * 
3638
    * @param   exc
3639
    *          Cause exception.
3640
    */
3641
   protected void handleExecuteException(ValidationException exc)
3642
   {
3643
      ErrorManager.recordOrThrowError(exc);
3644
   }
3645
   
3646
   /**
3647
    * This method is invoked when a persistence exception is caught during
3648
    * query execution.  This implementation delegates handling of the error
3649
    * to the <code>ErrorManager</code>.
3650
    * <p>
3651
    * Subclasses which require different handling must override this method.
3652
    * 
3653
    * @param   exc
3654
    *          Cause exception.
3655
    */
3656
   protected void handleExecuteException(PersistenceException exc)
3657
   {
3658
      ErrorManager.recordOrThrowError(exc);
3659
   }
3660
   
3661
   /**
3662
    * Obtain the {@link HQLHelper} object which supports this query, creating it first if
3663
    * necessary.
3664
    * <p>
3665
    * In particular, for LEFT OUTER-JOINs, when the helper is reset at each iteration, a new
3666
    * instance will be created unless all the elements from the argument list are not 
3667
    * {@code null} / {@code unknown}. In this case the cached instance will be reused.
3668
    *
3669
    * @return  HQL helper.
3670
    */
3671
   protected HQLHelper getHelper()
3672
   {
3673
      if (helper == null)
3674
      {
3675
         boolean hasUnknowns = false;
3676
         if (outer && dfArgs != null)
3677
         {
3678
            for (Object arg : dfArgs)
3679
            {
3680
               if (arg == null)
3681
               {
3682
                  hasUnknowns = true;
3683
                  break;
3684
               }
3685
               
3686
               if (arg instanceof FieldReference)
3687
               {
3688
                  arg = ((FieldReference) arg).resolve(); // NOTE: result is not saved
3689
               }
3690
               
3691
               if (arg instanceof BaseDataType && ((BaseDataType) arg).isUnknown())
3692
               {
3693
                  hasUnknowns = true;
3694
                  break;
3695
               }
3696
            }
3697
         }
3698
         
3699
         if (!hasUnknowns && nonNullArgsHelper != null)
3700
         {
3701
            // reuse the already built helper
3702
            helper = nonNullArgsHelper; 
3703
         }
3704
         else
3705
         {
3706
            helper = HQLHelper.obtain(buffer,
3707
                                      getReferencedBuffers(),
3708
                                      dfArgs,
3709
                                      isIdOnly(),
3710
                                      dfWhere,
3711
                                      sort,
3712
                                      join);
3713
            if (!hasUnknowns)
3714
            {
3715
               // cache it for future use when we encounter non-null args
3716
               nonNullArgsHelper = helper;
3717
            }
3718
         }
3719
         
3720
         if (helper.getSortIndex().isInverseSortPhrase(sort))
3721
         {
3722
            inverseSorting = true;
3723
         }
3724
      }
3725
      
3726
      return helper;
3727
   }
3728
   
3729
   /**
3730
    * Set as active the HQL bundle corresponding with a search for the single
3731
    * record, if any, which matches this query's criteria.
3732
    */
3733
   protected void activateUnique()
3734
   {
3735
      activeBundleKey = UNIQUE;
3736
      activeBundle = getHelper().unique();
3737
   }
3738
   
3739
   /**
3740
    * Set as active the HQL bundle corresponding with a search for the first
3741
    * record matching this query's criteria.
3742
    */
3743
   protected void activateFirst()
3744
   {
3745
      activeBundleKey = FIRST;
3746
      activeBundle = getHelper().first();
3747
   }
3748
   
3749
   /**
3750
    * Set as active the HQL bundle corresponding with a search for the last
3751
    * record matching this query's criteria.
3752
    */
3753
   private void activateLast()
3754
   {
3755
      activeBundleKey = LAST;
3756
      activeBundle = getHelper().last();
3757
   }
3758
   
3759
   /**
3760
    * Set as active the HQL bundle corresponding with a search for the next
3761
    * record matching this query's criteria, relative to the most recently
3762
    * fetched record.
3763
    */
3764
   private void activateNext()
3765
   {
3766
      activeBundleKey = NEXT;
3767
      activeBundle = getHelper().next();
3768
   }
3769
   
3770
   /**
3771
    * Set as active the HQL bundle corresponding with a search for the
3772
    * previous record matching this query's criteria, relative to the most
3773
    * recently fetched record.
3774
    */
3775
   private void activatePrevious()
3776
   {
3777
      activeBundleKey = PREVIOUS;
3778
      activeBundle = getHelper().previous();
3779
   }
3780
   
3781
   /**
3782
    * Resolve query substitution arguments in preparation for query execution.
3783
    * This method is invoked only once, at construction time, for simple
3784
    * queries (i.e., those corresponding with simple FIND statements in the
3785
    * original Progress code).  For iterating queries (i.e., those which
3786
    * correspond with FOR loops in the original Progress code), it is
3787
    * invoked once per each entrance into the loop.  Thus, for an outer loop,
3788
    * it is only invoked once, for an inner loop it is invoked once per each
3789
    * iteration of the <i>outer</i> loop, and so on.
3790
    * <p>
3791
    * This causes all variable (i.e. <code>BaseDataType</code>) arguments to
3792
    * be duplicated, and all <code>Resolvable</code>s, <i>except</i>
3793
    * <code>FieldReference</code>s, to be resolved and the result stored.
3794
    * <code>FieldReference</code>s are simply stored, and are resolved
3795
    * separately, on demand, each time the query is executed.
3796
    */
3797
   private void resolveArgs()
3798
   {
3799
      // nothing to process or there is an error pending
3800
      if (dfArgs == null || isFatalError())
3801
      {
3802
         return;
3803
      }
3804
      
3805
      // resolve regular query substitution parameters
3806
      int len = dfArgs.length;
3807
      int dLen = (dynamicFilterArgs == null) ? 0 : dynamicFilterArgs.size();
3808
      
3809
      currentArgs = new Object[len + dLen];
3810
      
3811
      // dynamic filters always come in front
3812
      if (dynamicFilterArgs != null)
3813
      {
3814
         for (int k = 0; k < dLen; k++)
3815
         {
3816
            currentArgs[k] = resolveArg(dynamicFilterArgs.get(k));
3817
         }
3818
      }
3819
      
3820
      for (int i = 0; i < len; i++)
3821
      {
3822
         currentArgs[dLen + i] = resolveArg(dfArgs[i]);
3823
      }
3824
   }
3825
   
3826
   /**
3827
    * Resolve a single resolvable query substitution parameter, with the exception of
3828
    * {@code FieldReference}s.
3829
    *
3830
    * @return  The resolved query substitution parameter or {@code arg} itself if it is an
3831
    *          instance of {@code FieldReference}.
3832
    */
3833
   private Object resolveArg(Object arg)
3834
   {
3835
      if (arg instanceof FieldReference)
3836
      {
3837
         return arg;
3838
      }
3839
      else if (arg instanceof Resolvable)
3840
      {
3841
         return ((Resolvable) arg).resolve();
3842
      }
3843
      else
3844
      {
3845
         return ((BaseDataType) arg).duplicate();
3846
      }
3847
   }
3848
   
3849
   /**
3850
    * Find the next record matching the search criteria, or the first such
3851
    * record if no previous search has been executed against this query.
3852
    * 
3853
    * @param   values
3854
    *          Array of substitution parameter values for the base where
3855
    *          clause (i.e., the un-augmented portion of the clause).
3856
    * @param   lockType
3857
    *          Type of lock to acquire for the found record.
3858
    * @param   updateLock
3859
    *          <code>true</code> if the status of the lock on the retrieved
3860
    *          record should be modified;  else <code>false</code>.  This is
3861
    *          set to <code>true</code> for actual retrievals, and to
3862
    *          <code>false</code> when only detecting whether a record would
3863
    *          be found, or when peeking a record.
3864
    *
3865
    * @return  Next (or first) record to match the query criteria.
3866
    */
3867
   private Record findNext(Object[] values, LockType lockType, boolean updateLock)
3868
   {
3869
      Record dmo = null;
3870
      FastFindKey ffk = null;
3871
      SortIndex sortIndex = getHelper().getSortIndex();
3872
      OffEnd offEnd = buffer.getOffEnd(sortIndex, inverseSorting);
3873
      
3874
      if (offEnd == OffEnd.FRONT || (buffer.getSnapshot() == null && referenceRecord == null))
3875
      {
3876
         if (ffEnable)
3877
         {
3878
            ffk = new FastFindKey(FIRST, index, null);
3879
            dmo = ffCache.get(ffk);
3880
            if (dmo != null)
3881
            {
3882
               return dmo;
3883
            }
3884
         }
3885
         
3886
         activateFirst();
3887
      }
3888
      else
3889
      {
3890
         activateNext();
3891
      }
3892
      
3893
      dmo = execute(values, lockType, false, updateLock);
3894
      
3895
      // save the ff result, if the case
3896
      if (ffk != null && dmo != null)
3897
      {
3898
         ffCache.put(ffk, dmo);
3899
      }
3900
      
3901
      return dmo;
3902
   }
3903
   
3904
   /**
3905
    * Find the previous record matching the search criteria, or the last such
3906
    * record if no previous search has been executed against this query.
3907
    *
3908
    * @param   values
3909
    *          Array of substitution parameter values for the base where
3910
    *          clause (i.e., the un-augmented portion of the clause).
3911
    * @param   lockType
3912
    *          Type of lock to acquire for the found record.
3913
    * @param   updateLock
3914
    *          <code>true</code> if the status of the lock on the retrieved
3915
    *          record should be modified;  else <code>false</code>.  This is
3916
    *          set to <code>true</code> for actual retrievals, and to
3917
    *          <code>false</code> when only detecting whether a record would
3918
    *          be found, or when peeking a record.
3919
    *
3920
    * @return  Previous (or last) record to match the query criteria.
3921
    */
3922
   private Record findPrevious(Object[] values, LockType lockType, boolean updateLock)
3923
   {
3924
      Record dmo = null;
3925
      FastFindKey ffk = null;
3926
      SortIndex sortIndex = getHelper().getSortIndex();
3927
      OffEnd offEnd = buffer.getOffEnd(sortIndex, inverseSorting);
3928
      
3929
      if (offEnd == OffEnd.BACK  || (buffer.getSnapshot() == null && referenceRecord == null))
3930
      {
3931
         if (ffEnable)
3932
         {
3933
            ffk = new FastFindKey(LAST, index, null);
3934
            dmo = ffCache.get(ffk);
3935
            if (dmo != null)
3936
            {
3937
               return dmo;
3938
            }
3939
         }
3940
         
3941
         activateLast();
3942
      }
3943
      else
3944
      {
3945
         activatePrevious();
3946
      }
3947
      
3948
      dmo = execute(values, lockType, false, updateLock);
3949
      
3950
      // save the ff result, if the case
3951
      if (ffk != null && dmo != null)
3952
      {
3953
         ffCache.put(ffk, dmo);
3954
      }
3955
      
3956
      return dmo;
3957
   }
3958
   
3959
   /**
3960
    * Retrieve a record, given an array of size one, containing a primary key
3961
    * ID value of the record to be retrieved, or the record itself.  Acquire
3962
    * the specified lock type.
3963
    *
3964
    * @param   data
3965
    *          Array containing a single primary key ID or DMO.
3966
    * @param   lockType
3967
    *          Type of lock to be applied to the record loaded.
3968
    *
3969
    * @return  The record associated with the given ID, or <code>null</code>
3970
    *          if there is no record matching the ID.
3971
    */
3972
   private Record loadByValue(Object[] data, LockType lockType)
3973
   {
3974
      return loadByValue(data, lockType, false);
3975
   }
3976
   
3977
   /**
3978
    * Retrieve a record, given an array of size one, containing a primary key
3979
    * ID value of the record to be retrieved, or the record itself.  Acquire
3980
    * the specified lock type.
3981
    *
3982
    * @param   data
3983
    *          Array containing a single primary key ID or DMO.
3984
    * @param   lockType
3985
    *          Type of lock to be applied to the record loaded.
3986
    * @param   silentIfNullId
3987
    *          If <code>true</code>, do not raise error if some of the provided
3988
    *          IDs are <code>null</code>. <code>null</code> IDs are valid for
3989
    *          queries with OUTER join.
3990
    * 
3991
    * @return  The record associated with the given ID, or <code>null</code>
3992
    *          if there is no record matching the ID.
3993
    */
3994
   private Record loadByValue(Object[] data, LockType lockType, boolean silentIfNullId)
3995
   {
3996
      Record dmo = null;
3997
      
3998
      try
3999
      {
4000
         if (data != null)
4001
         {
4002
            Object datum = data[0];
4003
            boolean isDMO = (datum instanceof Record);
4004
            
4005
            if (isDMO && lockType.equals(LockType.NONE))
4006
            {
4007
               dmo = (Record) datum;
4008
            }
4009
            else
4010
            {
4011
               Long id;
4012
               if (isDMO)
4013
               {
4014
                  dmo = (Record) datum;
4015
                  id = dmo.primaryKey();
4016
               }
4017
               else
4018
               {
4019
                  id = (Long) datum;
4020
               }
4021
               
4022
               if (id == null && silentIfNullId)
4023
               {
4024
                  releaseBuffers();
4025
                  return null;
4026
               }
4027
               else
4028
               {
4029
                  Persistence persistence = buffer.getPersistence();
4030
                  dmo = persistence.load(buffer.getDMOImplementationClass(), id, lockType, 0L, true, false);
4031
               }
4032
            }
4033
         }
4034
      }
4035
      catch (MissingRecordException exc)
4036
      {
4037
         // record must have been deleted in another context
4038
         dmo = null;
4039
      }
4040
      catch (PersistenceException exc)
4041
      {
4042
         ErrorManager.recordOrThrowError(exc);
4043
      }
4044
      
4045
      return dmo;
4046
   }
4047
   
4048
   /**
4049
    * Create an array of size one, containing the primary key ID of the given
4050
    * DMO.
4051
    *
4052
    * @param   dmo
4053
    *          Data record whose ID is to be stored in the array.
4054
    *
4055
    * @return  Single element array or <code>null</code> if <code>dmo</code>
4056
    *          is <code>null</code>.
4057
    */
4058
   private Object[] getIDs(Record dmo)
4059
   {
4060
      if (dmo == null)
4061
      {
4062
         return null;
4063
      }
4064
      
4065
      return (new Object[] { dmo.primaryKey() });
4066
   }
4067
   
4068
   /**
4069
    * Record or throw a FIND FIRST/LAST failed error condition for the
4070
    * current DMO type.
4071
    *
4072
    * @throws  ErrorConditionException
4073
    *          if silent error mode is suppressed.
4074
    */
4075
   private void errorFindFirstLast()
4076
   {
4077
      String msg = "FIND FIRST/LAST failed for table " + buffer.getLegacyName();
4078
      ErrorManager.recordOrThrowError(565, msg);
4079
   }
4080
   
4081
   /**
4082
    * Execute each query stored in the active HQL bundle in turn until we
4083
    * find a result or have no more statements to execute.  Each statement is
4084
    * decreasingly specific.
4085
    * <p>
4086
    * The latest record to be stored in the record buffer, if any, marks our
4087
    * current position in the query's results.  Its data is used to substitute
4088
    * into the placeholder parameters in the query statements' augmented where
4089
    * clauses.  These are added to the <code>values</code> provided as part of
4090
    * the base query.
4091
    *
4092
    * @param   values
4093
    *          Array of substitution parameter values for the base where
4094
    *          clause (i.e., the un-augmented portion of the clause).
4095
    * @param   lockType
4096
    *          Type of lock to apply to the found record.
4097
    * @param   unique
4098
    *          <code>true</code> if there should be no more than one match
4099
    *          for the conditions specified;  <code>false</code> if multiple
4100
    *          matches are possible.
4101
    * @param   updateLock
4102
    *          <code>true</code> if the status of the lock on the retrieved
4103
    *          record should be modified;  else <code>false</code>.  This is
4104
    *          set to <code>true</code> for actual retrievals, and to
4105
    *          <code>false</code> when only detecting whether a record would
4106
    *          be found (i.e., the <code>hasXXXX()</code> methods).
4107
    *
4108
    * @return  The record resulting from the query, or <code>null</code> if
4109
    *          no record was found.
4110
    *
4111
    * @throws  ErrorConditionException
4112
    *          if a recoverable error occurred while executing.
4113
    */
4114
   private Record executeImpl(Object[] values,
4115
                              LockType lockType,
4116
                              boolean unique,
4117
                              boolean updateLock)
4118
   {
4119
      boolean debug = LOG.isLoggable(Level.FINE);
4120
      
4121
      DirtyShareContext dirtyContext = buffer.getDirtyContext();
4122
      ParameterIndices pi = activeBundle.getParameterIndices();
4123
      int origArgCount = (values == null ? 0 : values.length);
4124
      int baseArgCount = (pi == null ? origArgCount : pi.getCount());
4125
      
4126
      Record placeholder = referenceRecord != null ? referenceRecord : buffer.getSnapshot();
4127
      Record dmo = null;
4128
      Set<Record> primaryDMOs = null;
4129
      
4130
      try
4131
      {
4132
         List<Serializable> parameters = null;
4133
         List<FqlType> parameterTypes = null;
4134
         
4135
         if (join != null)
4136
         {
4137
            parameters = join.getParameters();
4138
            parameterTypes = join.getParameterTypes();
4139
            
4140
            if (parameterTypes != null)
4141
            {
4142
               int s = parameterTypes.size();
4143
               baseArgCount += s;
4144
               origArgCount += s;
4145
            }
4146
         }
4147
         
4148
         prepareBuffer();
4149
         Persistence persistence = buffer.getPersistence();
4150
         boolean multiplex = buffer.isMultiplexed();
4151
         
4152
         if (multiplex)
4153
         {
4154
            baseArgCount++;
4155
         }
4156
         
4157
         Object[] baseArgs = new Object[baseArgCount];
4158
         int startIndex = 0;
4159
         
4160
         if (multiplex)
4161
         {
4162
            startIndex++;
4163
            baseArgs[0] = buffer.getMultiplexID();
4164
         }
4165
         
4166
         // Add the join parameters (if available) as the next arguments.
4167
         if (join != null && parameterTypes != null)
4168
         {
4169
            int size = parameterTypes.size();
4170
            for (int i = 0; i < size; i++)
4171
            {
4172
               baseArgs[startIndex] = parameters.get(i);
4173
               startIndex++;
4174
            }
4175
         }
4176
         
4177
         HQLPreprocessor hqlPreproc = getHelper().getHQLPreprocessor();
4178
         List<String> earlyPublish = hqlPreproc.getEarlyPublishEntities();
4179
         if (dirtyContext != null && !earlyPublish.isEmpty())
4180
         {
4181
            List<Record> dmos = dirtyContext.updateSnapshots(earlyPublish, persistence);
4182
            
4183
            // make sure DMOs do not bloat the session
4184
            for (Record next : dmos)
4185
            {
4186
               buffer.evictDMOIfUnused(next);
4187
            }
4188
         }
4189
         
4190
         boolean needsLock = updateLock && lockType != LockType.NONE;
4191
         boolean findByRowid = hqlPreproc.isFindByRowid();
4192
         
4193
         while (true)
4194
         {
4195
            // Iterate all arguments provided for the base portion of the
4196
            // where clause only.  If any of these are FieldReferences,
4197
            // resolve these parameters.
4198
            for (int j = 0, i = startIndex; i < baseArgCount; j++)
4199
            {
4200
               int k = -1;
4201
               if (pi == null)
4202
               {
4203
                  k = j;
4204
               }
4205
               else
4206
               {
4207
                  Integer idx = pi.getIndex(j);
4208
                  if (idx == null)
4209
                  {
4210
                     // Original substitution parameter was inlined into HQL
4211
                     // by preprocessor;  skip it.
4212
                     continue;
4213
                  }
4214
                  k = idx;
4215
               }
4216
               
4217
               // Substitution placeholders may have been reordered if this
4218
               // statement was rewritten by the HQL preprocessor. Make sure
4219
               // we access the correct substitution parameter.
4220
               Object next = values[k];
4221
               
4222
               // Store parameters in the base args array, resolving them
4223
               // first if necessary.
4224
               if (next instanceof Resolvable)
4225
               {
4226
                  baseArgs[i] = ((Resolvable) next).resolve();
4227
               }
4228
               else
4229
               {
4230
                  baseArgs[i] = next;
4231
               }
4232
               
4233
               i++;
4234
            }
4235
            
4236
            // determine call count
4237
            int callCount = (placeholder != null ? activeBundle.gettersSize() : 0);
4238
            
4239
            // Collect all arguments required by the augmented portion of the
4240
            // where clause only, by invoking getters on the latest DMO stored
4241
            // in the buffer.
4242
            Object[] augmentArgs = new Object[callCount];
4243
            Iterator<Method> getters = activeBundle.getters();
4244
            for (int i = 0; getters.hasNext(); i++)
4245
            {
4246
               Method method = getters.next();
4247
               
4248
               // Arguments to this call can be null, because the augmented
4249
               // part of the where is based on non-extent properties only.
4250
               augmentArgs[i] = method.invoke(placeholder);
4251
            }
4252
            
4253
            // Collect the full arrays of parameters and types, including base
4254
            // arguments and augmented.  In the loop below, we will copy the
4255
            // necessary portions of these arrays to temporary ones.
4256
            int fullCount = baseArgCount + callCount;
4257
            Object[] allArgs = new Object[fullCount];
4258
            
4259
            // copy base parameters and types into full arrays
4260
            System.arraycopy(baseArgs, 0, allArgs, 0, baseArgCount);
4261
            
4262
            // copy augmented parameters into full array
4263
            System.arraycopy(augmentArgs, 0, allArgs, baseArgCount, callCount);
4264
            
4265
            // possibly overriding result from uncommitted transaction in another session
4266
            DirtyInfo info = null;
4267
            
4268
            // execute HQL statements in turn, until we find a result (or run out of statements)
4269
            Iterator<String> statements = activeBundle.statements();
4270
            while (dmo == null && info == null && statements.hasNext())
4271
            {
4272
               String hql = statements.next();
4273
               int length = baseArgCount + callCount;
4274
               Object[] parms = new Object[length];
4275
               
4276
               // copy arguments needed for this pass from full argument list
4277
               System.arraycopy(allArgs, 0, parms, 0, length);
4278
               
4279
               if (callCount > 0)
4280
               {
4281
                  callCount--;
4282
               }
4283
               
4284
               FibonacciCounter fib;
4285
               long timeout;
4286
               
4287
               needsLock = updateLock && lockType != LockType.NONE;
4288
               if (needsLock)
4289
               {
4290
                  fib = new FibonacciCounter();
4291
                  timeout = fib.next();
4292
               }
4293
               else
4294
               {
4295
                  fib = null;
4296
                  timeout = 0L;
4297
               }
4298
               
4299
               do
4300
               {
4301
                  // find and optionally lock the record
4302
                  try
4303
                  {
4304
                     dmo = persistence.load(buffer,
4305
                                            hql,
4306
                                            parms,
4307
                                            lockType,
4308
                                            timeout,
4309
                                            unique,
4310
                                            updateLock,
4311
                                            false,
4312
                                            findByRowid);
4313
                     
4314
                     // either got the lock or didn't need one
4315
                     needsLock = false;
4316
                     
4317
                     break;
4318
                  }
4319
                  catch (LockTimeoutException exc)
4320
                  {
4321
                     // a DMO was found but could not be locked and we could not determine it
4322
                     // has been deleted in an uncommitted transaction in another session
4323
                     RecordIdentifier ident = exc.getIdentifier();
4324
                     
4325
                     if (LOG.isLoggable(Level.FINE))
4326
                     {
4327
                        LOG.fine("[" +
4328
                                 Utils.describeContext() +
4329
                                 "] FIND lock timeout: "
4330
                                 + timeout
4331
                                 + " ms: "
4332
                                 + ident);
4333
                     }
4334
                     Long id = ident.getRecordID();
4335
                     String entity = buffer.getEntityName();
4336
                     RecordIdentifier entityIdent = new RecordIdentifier(entity, id);
4337
                     
4338
                     // try to load the DMO from our session
4339
                     dmo = persistence.quickLoad(entityIdent);
4340
                     
4341
                     // If the record came back null, this indicates another session deleted it
4342
                     // and has since (as in, since we timed out on the lock but before we were
4343
                     // able to quick-load the DMO) committed its transaction. We have to
4344
                     // execute the current query again with the same arguments to try to get
4345
                     // (and lock) another qualifying record. Don't recalculate the timeout in
4346
                     // this case.
4347
                     
4348
                     // if DMO came back non-null, we need to hold a reference to it temporarily
4349
                     // as a placeholder, in case we have to execute another query
4350
                     if (dmo != null)
4351
                     {
4352
                        // evict from the session if not otherwise in use
4353
                        buffer.evictDMOIfUnused(dmo);
4354
                        
4355
                        if (dirtyContext != null && dirtyContext.isDirtyDelete(entity, id, true))
4356
                        {
4357
                           // a record was found but it is being deleted in another session's
4358
                           // uncommitted transaction
4359
                           info = new DirtyInfo();
4360
                           info.setDeleted();
4361
                           
4362
                           // NOTE: we are breaking out of the load/lock loop WITHOUT acquiring
4363
                           // the requested lock! This only works because the logic below forces
4364
                           // the DMO back to null before we can use it. We don't null it here
4365
                           // because we may need it as a placeholder record for another pass.
4366
                           break;
4367
                        }
4368
                     }
4369
                     
4370
                     // prepare for another pass
4371
                     if (dmo == null)
4372
                     {
4373
                        // if same query is to be executed again, reset the timeout value
4374
                        fib.reset();
4375
                        timeout = fib.next();
4376
                     }
4377
                     else
4378
                     {
4379
                        // otherwise, steadily increase the timeout value
4380
                        timeout = fib.next();
4381
                        
4382
                        // log deadlock warning if timeout is getting too long and wait
4383
                        // indefinitely
4384
                        if (timeout > 3600000L)
4385
                        {
4386
                           timeout = 0L;
4387
                           if (LOG.isLoggable(Level.WARNING))
4388
                           {
4389
                              LOG.log(Level.WARNING,
4390
                                      "[" +
4391
                                      Utils.describeContext() +
4392
                                      "] Possible deadlocked record: " + ident,
4393
                                      new Throwable());
4394
                           }
4395
                        }
4396
                     }
4397
                  }
4398
                  catch (MissingRecordException exc)
4399
                  {
4400
                     if (needsLock)
4401
                     {
4402
                        // we got the lock, but the record was deleted in another session's
4403
                        // transaction, which has since been committed; reset the timeout and
4404
                        // try to find another qualifying record
4405
                        fib.reset();
4406
                        timeout = fib.next();
4407
                     }
4408
                     else
4409
                     {
4410
                        break;
4411
                     }
4412
                  }
4413
               } while (true);
4414
               
4415
               if (DatabaseStatistics.isEnabled())
4416
               {
4417
                  Database database = buffer.getDatabase();
4418
                  DatabaseStatistics.get().queryExecuted(this, database, hql);
4419
               }
4420
               
4421
               if (debug)
4422
               {
4423
                  logQueryDetails(hql, parms, dmo);
4424
               }
4425
            }
4426
            
4427
            if (dmo != null           &&
4428
                callCount == 0        &&
4429
                augmentArgs != null   &&
4430
                augmentArgs.length > 0)
4431
            {
4432
               // This won't pick up break value for FIRST and LAST, but we're
4433
               // only concerned with NEXT and PREV anyway.
4434
               breakValue = augmentArgs[0];
4435
            }
4436
            
4437
            // check the dirty share manager for an overriding result
4438
            // TODO: checking the number of referenced buffers is a temporary workaround;
4439
            //       a query with server-side joins will not return the right information from the
4440
            //       dirty database and may cause a fatal error, if the joined table does not yet
4441
            //       exist in the dirty database; however, this is not a correct solution!
4442
            if (info == null && dirtyContext != null && getReferencedBuffers().length == 1)
4443
            {
4444
               info = dirtyContext.getDirtyInfo(buffer,
4445
                                                index,
4446
                                                activeBundleKey,
4447
                                                activeBundle,
4448
                                                allArgs,
4449
                                                dmo,
4450
                                                hqlPreproc.isFindByRowid());
4451
            }
4452
            
4453
            // if check found nothing of interest, we're done
4454
            if (info == null)
4455
            {
4456
               break;
4457
            }
4458
            
4459
            // keep track of the DMO(s) we found in the primary database; in the event we don't
4460
            // use them otherwise, we'll have to evict them from the session
4461
            if (dmo != null)
4462
            {
4463
               if (primaryDMOs == null)
4464
               {
4465
                  primaryDMOs = new HashSet<>();
4466
               }
4467
               
4468
               primaryDMOs.add(dmo);
4469
            }
4470
            
4471
            // can't trigger revalidation of AdaptiveQuery if dirty database result is in use
4472
            breakValue = null;
4473
            
4474
            // Get the DMO found in the dirty database, if any.  This will be
4475
            // a deep copy of the original, so it is safe to use.
4476
            Record dirtyDMO = info.getDirtyDMO();
4477
            
4478
            // If found DMO was deleted, update the placeholder and try again.
4479
            // Also handle the case where the DMO found in the primary
4480
            // database has been modified in such a way that it may no longer
4481
            // satisfy the search criteria.
4482
            if (activeBundleKey != UNIQUE &&
4483
                (info.isDeleted() ||
4484
                 (info.isModified() &&
4485
                  (!info.isInserted() ||
4486
                   (dirtyDMO != null && dirtyDMO.primaryKey().equals(dmo.primaryKey()))))))
4487
            {
4488
               // Test if the record has moved along the index we're walking.
4489
               if (info.isModified() && dirtyDMO != null)
4490
               {
4491
                  Record compDirtyDMO = info.getComparableDMO();
4492
                  int comp = dmoSorter.compare(dmo, compDirtyDMO);
4493
                  if (comp == 0)
4494
                  {
4495
                     // Both the primary DMO and dirty DMO sort equivalently
4496
                     // on the index being navigated.  If the dirty DMO does
4497
                     // not represent the full set of uncommitted changes
4498
                     // being tracked in the dirty database, that indicates we
4499
                     // published uncommitted changes early and we must use
4500
                     // the dirty DMO as our result...
4501
                     if (!info.isFullyPublished())
4502
                     {
4503
                        dmo = dirtyDMO;
4504
                        dirtyCopy = true;
4505
                     }
4506
                     
4507
                     // ...otherwise, we use the primary DMO.
4508
                     break;
4509
                  }
4510
               }
4511
               
4512
               // We are about to update the placeholder and try another pass,
4513
               // so if the navigation request was for the first or last
4514
               // record, we have to modify the mode to be next or previous,
4515
               // respectively.  Otherwise, we'll just find the same record in
4516
               // an infinite loop.
4517
               switch (activeBundleKey)
4518
               {
4519
                  case FIRST:
4520
                     activateNext();
4521
                     break;
4522
                  case LAST:
4523
                     activatePrevious();
4524
                     break;
4525
               }
4526
               
4527
               // The found record must be skipped because it was either
4528
               // deleted or modified on the index we're walking.  Update the
4529
               // placeholder to be the record we actually found...
4530
               placeholder = dmo;
4531
               dmo = null;
4532
               
4533
               // ...then try another pass.
4534
               continue;
4535
            }
4536
            
4537
            if (needsLock && dmo != null)
4538
            {
4539
               // If we are here, we determined a provisional DMO we found was deleted in another
4540
               // session's uncommitted transaction and we broke out of the load/lock loop
4541
               // without acquiring the requested lock. The DMO was loaded only for placeholder
4542
               // purposes. Null it out now, since we haven't got the lock; we will either find
4543
               // another qualifying record or give up. A bit redundant with the delete logic
4544
               // in processDirtyResults, but safety first.
4545
               dmo = null;
4546
            }
4547
            
4548
            // determine which of the two candidate DMOs is most appropriate as a result
4549
            dmo = processDirtyResults(persistence, dmo, info, lockType, updateLock);
4550
            
4551
            break;
4552
         }
4553
         
4554
         if (dirtyCopy && primaryDMOs != null)
4555
         {
4556
            // We are not going to use the primary DMO(s) we found, so make
4557
            // sure they are evicted from the session if not in use elsewhere
4558
            // in this context.
4559
            for (Record pDMO : primaryDMOs)
4560
            {
4561
               buffer.evictDMOIfUnused(pDMO);
4562
            }
4563
         }
4564
      }
4565
      catch (IllegalAccessException exc)
4566
      {
4567
         throw new RuntimeException(exc);
4568
      }
4569
      catch (InvocationTargetException exc)
4570
      {
4571
         DBUtils.handleException(buffer.getDatabase(), exc);
4572
         
4573
         throw new RuntimeException(exc);
4574
      }
4575
      catch (LockUnavailableException exc)
4576
      {
4577
         handleExecuteException(exc);
4578
         
4579
         // Cannot return record if it could not be locked.
4580
         dmo = null;
4581
      }
4582
      catch (ValidationException exc)
4583
      {
4584
         // a validation error can only result from flushing an unrelated record currently in
4585
         // the buffer in preparation for a query result; since this will not be within the
4586
         // context of any silent error mode under which this query is running, we instruct
4587
         // the error manager to not suppress the message
4588
         ErrorManager.throwError(exc, false);
4589
      }
4590
      catch (PersistenceException exc)
4591
      {
4592
         handleExecuteException(exc);
4593
      }
4594
      finally
4595
      {
4596
         if (dmo == null)
4597
         {
4598
            breakValue = null;
4599
         }
4600
      }
4601
      
4602
      return dmo;
4603
   }
4604
   
4605
   /**
4606
    * Process the results of a dirty index check and return the most
4607
    * appropriate result for the query.  This method may acquire a record lock
4608
    * for the selected result.
4609
    * 
4610
    * @param   persistence
4611
    *          Persistence services object.
4612
    * @param   primaryDMO
4613
    *          Candidate DMO found in the primary database.  Will not be
4614
    *          <code>null</code>.
4615
    * @param   info
4616
    *          Information found during the dirty database check.
4617
    * @param   lockType
4618
    *          Type of lock to apply to the found record.
4619
    * @param   updateLock
4620
    *          <code>true</code> if the status of the lock on the retrieved
4621
    *          record should be modified;  else <code>false</code>.  This is
4622
    *          set to <code>true</code> for actual retrievals, and to
4623
    *          <code>false</code> when only detecting whether a record would
4624
    *          be found (i.e., the <code>hasXXXX()</code> methods).
4625
    * 
4626
    * @return  Either <code>primaryDMO</code> or <code>dirtyDMO</code>,
4627
    *          depending upon which record is most appropriate in terms of the
4628
    *          index being walked, and possibly the most recently found
4629
    *          record, in the case of a relative move (NEXT/PREVIOUS).
4630
    * 
4631
    * @throws  PersistenceException
4632
    *          if any error occurs retrieving a record from the primary
4633
    *          database, after determining its ID in the dirty database.
4634
    * @throws  LockUnavailableException
4635
    *          if a no-wait lock requested on <code>dirtyDMO</code> is
4636
    *          currently unavailable.
4637
    */
4638
   private Record processDirtyResults(Persistence persistence,
4639
                                      Record primaryDMO,
4640
                                      DirtyInfo info,
4641
                                      LockType lockType,
4642
                                      boolean updateLock)
4643
   throws PersistenceException
4644
   {
4645
      Record dmo = primaryDMO;
4646
      Record dirtyDMO = info.getDirtyDMO();
4647
      Record compDirtyDMO = info.getComparableDMO();
4648
      boolean useDirtyDMO = true;
4649
      
4650
      if (primaryDMO != null && activeBundleKey != UNIQUE)
4651
      {
4652
         // At this point, we have both a candidate DMO from the primary
4653
         // database (dmo), and a candidate DMO from the dirty database
4654
         // (dirtyDMO).  We need to sort them to determine which one is
4655
         // the appropriate "next" record in the index being walked.
4656
         int comp = dmoSorter.compare(primaryDMO, compDirtyDMO);
4657
         if (activeBundleKey == LAST || activeBundleKey == PREVIOUS)
4658
         {
4659
            comp *= -1;
4660
         }
4661
         
4662
         useDirtyDMO = (comp >= 0);
4663
      }
4664
      
4665
      if (useDirtyDMO)
4666
      {
4667
         if (info.isInserted() && (info.isLocal() || LockType.NONE.equals(lockType)))
4668
         {
4669
            // Use the dirty copy if no lock has been requested, or if the record found in the
4670
            // dirty database is local to the session.
4671
            // In the latter case (info.isLocal() == true), the dirty record actually is the
4672
            // local original. This comes into play when a DMO is created in one buffer and is
4673
            // queried by another buffer in the same session, before the first buffer has
4674
            // validated and flushed the record (it is validated as a side effect of the
4675
            // getDirtyInfo() call). The record is not found in primary database (since it has
4676
            // not yet been flushed), but it is found in the dirty database, and the dirty
4677
            // context substitutes the original DMO for the dirty one in getDirtyInfo().
4678
            dmo = dirtyDMO;
4679
            
4680
            // mark the record as dirty and potentially from another context; we do this even in
4681
            // the local case, since this prevents us from trying to reassociate the record with
4682
            // the current Hibernate session more than once, in certain cases (see
4683
            // Persistence$Context.getSession())
4684
            dirtyCopy = true;
4685
         }
4686
         else if (dirtyDMO != null)
4687
         {
4688
            // Just use the primary key of the record found in the dirty
4689
            // database, but reload the record from the primary database.
4690
            // Note:  it might not be there, if the record existed only in
4691
            // another context, and was never committed.
4692
            Long id = dirtyDMO.primaryKey();
4693
            try
4694
            {
4695
               dmo = persistence.load(buffer.getDMOImplementationClass(), id, lockType, 0L, updateLock, false);
4696
            }
4697
            catch (MissingRecordException exc)
4698
            {
4699
               dmo = null;
4700
            }
4701
         }
4702
         else if (info.isDeleted() || info.isModified())
4703
         {
4704
            dmo = null;
4705
         }
4706
      }
4707
      
4708
      return dmo;
4709
   }
4710
   
4711
   /**
4712
    * Log debug details about the inputs and results of a query.
4713
    * 
4714
    * @param   hql
4715
    *          HQL query statement.
4716
    * @param   parms
4717
    *          Query substitution parameters
4718
    * @param   dmo
4719
    *          DMO record found, if any.
4720
    */
4721
   private void logQueryDetails(String hql, Object[] parms, Record dmo)
4722
   {
4723
      LOG.log(Level.FINE, "HQL:  " + hql);
4724
      
4725
      StringBuilder buf = new StringBuilder("PARMS:  ");
4726
      int len = parms.length;
4727
      if (len == 0)
4728
      {
4729
         buf.append("N/A");
4730
      }
4731
      
4732
      for (int k = 0; k < len; k++)
4733
      {
4734
         if (k > 0)
4735
         {
4736
            buf.append(", ");
4737
         }
4738
         Object next = parms[k];
4739
         if (next instanceof BaseDataType)
4740
         {
4741
            buf.append(((BaseDataType) next).toStringMessage());
4742
         }
4743
         else
4744
         {
4745
            buf.append(next);
4746
         }
4747
      }
4748
      
4749
      LOG.log(Level.FINE, buf.toString());
4750
      
4751
      buf.setLength(0);
4752
      buf.append("DMO:  ");
4753
      if (dmo != null)
4754
      {
4755
         buf.append(dmo.primaryKey());
4756
      }
4757
      else
4758
      {
4759
         buf.append("<not found>");
4760
      }
4761
      
4762
      LOG.log(Level.FINE, buf.toString());
4763
      
4764
      if (LOG.isLoggable(Level.FINEST))
4765
      {
4766
         buf.setLength(0);
4767
         buf.append("DMO Detail:  ");
4768
         buf.append(buffer.toString(dmo));
4769
         
4770
         LOG.log(Level.FINEST, buf.toString());
4771
      }
4772
   }
4773
   
4774
   /**
4775
    * A key for Fast Find algorithm. It uses this class as cache key for quickly checking whether a particular
4776
    * record was found recently.
4777
    */
4778
   private class FastFindKey
4779
   {
4780
      /** The DMO interface of the keyed record. */
4781
      private final Class<? extends DataModelObject> dmo;
4782
      
4783
      /** The hql of the query which selected the keyed record. */
4784
      private final String hql;
4785
      
4786
      /** The type of navigation. Must be one of FIRST, LAST or UNIQUE values of the {@code QueryConstants} */
4787
      private final int type;
4788
      
4789
      /** The index used when executing the query.*/
4790
      private final String index;
4791
      
4792
      /** The substitution values. May be null.*/
4793
      private final Object[] values;
4794
      
4795
      /** The precomputed hash code. */
4796
      private final int hash;
4797
      
4798
      /** The unique constructor builds the immutable key. */
4799
      public FastFindKey(int type, String index, Object[] values)
4800
      {
4801
         this.dmo = buffer.getDMOInterface();
4802
         this.hql = getHelper().getHQLPreprocessor().getHQL().toFinalExpression();
4803
         this.type = type;
4804
         this.index = index;
4805
         this.values = values;
4806
         
4807
         int lHash = dmo.hashCode();
4808
         lHash = 31 * lHash + hql.hashCode();
4809
         lHash = 31 * lHash + type;
4810
         lHash = 31 * lHash + (index != null ? index.hashCode() : 0);
4811
         lHash = 31 * lHash + Arrays.hashCode(values);
4812
         this.hash = lHash;
4813
      }
4814
      
4815
      /**
4816
       * Test for equality with another object.
4817
       * 
4818
       * @param   o
4819
       *          The other object.
4820
       *
4821
       * @return  {@code true} if and only if the objects are equals.
4822
       */
4823
      @Override
4824
      public boolean equals(Object o)
4825
      {
4826
         if (this == o)
4827
         {
4828
            return true;
4829
         }
4830
         if (o == null || o.getClass() != FastFindKey.class)
4831
         {
4832
            return false;
4833
         }
4834
         
4835
         FastFindKey that = (FastFindKey) o;
4836
         
4837
         if (hash != that.hash)
4838
         {
4839
            return false;
4840
         }
4841
         if (type != that.type)
4842
         {
4843
            return false;
4844
         }
4845
         if (!dmo.equals(that.dmo))
4846
         {
4847
            return false;
4848
         }
4849
         if (!hql.equals(that.hql))
4850
         {
4851
            return false;
4852
         }
4853
         if (index != null ? !index.equals(that.index) : that.index != null)
4854
         {
4855
            return false;
4856
         }
4857
         
4858
         return Arrays.equals(values, that.values);
4859
      }
4860
      
4861
      /**
4862
       * Obtain the precomputed hash code.
4863
       * 
4864
       * @return  the precomputed hash code.
4865
       */
4866
      @Override
4867
      public int hashCode()
4868
      {
4869
         return hash;
4870
      }
4871
   }
4872
}