RandomAccessQuery.java
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 |
} |