6
6
*/
7
7
package org .hibernate .community .dialect ;
8
8
9
+ import java .util .ArrayList ;
9
10
import java .util .List ;
10
11
11
- import org .hibernate .LockMode ;
12
12
import org .hibernate .engine .spi .SessionFactoryImplementor ;
13
13
import org .hibernate .internal .util .collections .Stack ;
14
+ import org .hibernate .metamodel .mapping .EmbeddableValuedModelPart ;
15
+ import org .hibernate .metamodel .mapping .EntityIdentifierMapping ;
16
+ import org .hibernate .metamodel .mapping .EntityMappingType ;
14
17
import org .hibernate .metamodel .mapping .JdbcMappingContainer ;
15
18
import org .hibernate .query .IllegalQueryOperationException ;
16
19
import org .hibernate .query .sqm .ComparisonOperator ;
24
27
import org .hibernate .sql .ast .tree .cte .CteMaterialization ;
25
28
import org .hibernate .sql .ast .tree .expression .CaseSearchedExpression ;
26
29
import org .hibernate .sql .ast .tree .expression .ColumnReference ;
27
- import org .hibernate .sql .ast .tree .expression .AggregateColumnWriteExpression ;
28
30
import org .hibernate .sql .ast .tree .expression .Expression ;
29
31
import org .hibernate .sql .ast .tree .expression .FunctionExpression ;
30
32
import org .hibernate .sql .ast .tree .expression .Literal ;
31
33
import org .hibernate .sql .ast .tree .expression .Over ;
32
34
import org .hibernate .sql .ast .tree .expression .SqlTuple ;
33
35
import org .hibernate .sql .ast .tree .expression .SqlTupleContainer ;
34
36
import org .hibernate .sql .ast .tree .expression .Summarization ;
37
+ import org .hibernate .sql .ast .tree .from .FromClause ;
35
38
import org .hibernate .sql .ast .tree .from .FunctionTableReference ;
36
- import org .hibernate .sql .ast .tree .from .NamedTableReference ;
37
39
import org .hibernate .sql .ast .tree .from .QueryPartTableReference ;
40
+ import org .hibernate .sql .ast .tree .from .TableGroup ;
38
41
import org .hibernate .sql .ast .tree .from .UnionTableGroup ;
39
42
import org .hibernate .sql .ast .tree .from .ValuesTableReference ;
40
43
import org .hibernate .sql .ast .tree .insert .InsertSelectStatement ;
41
44
import org .hibernate .sql .ast .tree .insert .Values ;
45
+ import org .hibernate .sql .ast .tree .predicate .InSubQueryPredicate ;
46
+ import org .hibernate .sql .ast .tree .predicate .Predicate ;
42
47
import org .hibernate .sql .ast .tree .select .QueryGroup ;
43
48
import org .hibernate .sql .ast .tree .select .QueryPart ;
44
49
import org .hibernate .sql .ast .tree .select .QuerySpec ;
45
50
import org .hibernate .sql .ast .tree .select .SelectClause ;
46
51
import org .hibernate .sql .ast .tree .select .SortSpecification ;
47
52
import org .hibernate .sql .ast .tree .update .Assignment ;
48
53
import org .hibernate .sql .exec .spi .JdbcOperation ;
54
+ import org .hibernate .sql .results .internal .SqlSelectionImpl ;
49
55
import org .hibernate .type .SqlTypes ;
50
56
51
57
/**
@@ -97,12 +103,6 @@ protected LockStrategy determineLockingStrategy(
97
103
Boolean followOnLocking ) {
98
104
LockStrategy strategy = super .determineLockingStrategy ( querySpec , forUpdateClause , followOnLocking );
99
105
final boolean followOnLockingDisabled = Boolean .FALSE .equals ( followOnLocking );
100
- if ( strategy != LockStrategy .FOLLOW_ON && querySpec .hasSortSpecifications () ) {
101
- if ( followOnLockingDisabled ) {
102
- throw new IllegalQueryOperationException ( "Locking with ORDER BY is not supported" );
103
- }
104
- strategy = LockStrategy .FOLLOW_ON ;
105
- }
106
106
// Oracle also doesn't support locks with set operators
107
107
// See https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm#i2066346
108
108
if ( strategy != LockStrategy .FOLLOW_ON && isPartOfQueryGroup () ) {
@@ -117,29 +117,12 @@ protected LockStrategy determineLockingStrategy(
117
117
}
118
118
strategy = LockStrategy .FOLLOW_ON ;
119
119
}
120
- if ( strategy != LockStrategy .FOLLOW_ON && useOffsetFetchClause ( querySpec ) && !isRowsOnlyFetchClauseType ( querySpec ) ) {
120
+ if ( strategy != LockStrategy .FOLLOW_ON && needsLockingWrapper ( querySpec ) && !canApplyLockingWrapper ( querySpec ) ) {
121
121
if ( followOnLockingDisabled ) {
122
- throw new IllegalQueryOperationException ( "Locking with FETCH is not supported" );
122
+ throw new IllegalQueryOperationException ( "Locking with OFFSET/ FETCH is not supported" );
123
123
}
124
124
strategy = LockStrategy .FOLLOW_ON ;
125
125
}
126
- if ( strategy != LockStrategy .FOLLOW_ON ) {
127
- final boolean hasOffset ;
128
- if ( querySpec .isRoot () && hasLimit () && getLimit ().getFirstRow () != null ) {
129
- hasOffset = true ;
130
- // We must record that the generated SQL depends on the fact that there is an offset
131
- addAppliedParameterBinding ( getOffsetParameter (), null );
132
- }
133
- else {
134
- hasOffset = querySpec .getOffsetClauseExpression () != null ;
135
- }
136
- if ( hasOffset ) {
137
- if ( followOnLockingDisabled ) {
138
- throw new IllegalQueryOperationException ( "Locking with OFFSET is not supported" );
139
- }
140
- strategy = LockStrategy .FOLLOW_ON ;
141
- }
142
- }
143
126
return strategy ;
144
127
}
145
128
@@ -166,7 +149,7 @@ protected boolean supportsNestedSubqueryCorrelation() {
166
149
167
150
protected boolean shouldEmulateFetchClause (QueryPart queryPart ) {
168
151
// Check if current query part is already row numbering to avoid infinite recursion
169
- if (getQueryPartForRowNumbering () == queryPart ) {
152
+ if ( getQueryPartForRowNumbering () == queryPart ) {
170
153
return false ;
171
154
}
172
155
final boolean hasLimit = queryPart .isRoot () && hasLimit () || queryPart .getFetchClauseExpression () != null
@@ -176,77 +159,12 @@ protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
176
159
}
177
160
// Even if Oracle supports the OFFSET/FETCH clause, there are conditions where we still want to use the ROWNUM pagination
178
161
if ( supportsOffsetFetchClause () ) {
179
- // When the query has no sort specifications and offset, we want to use the ROWNUM pagination as that is a special locking case
180
- return !queryPart .hasSortSpecifications () && !hasOffset ( queryPart )
181
- // Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
182
- || queryPart instanceof QueryGroup && getClauseStack ().isEmpty () && getStatement () instanceof InsertSelectStatement ;
162
+ // Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
163
+ return queryPart instanceof QueryGroup && getClauseStack ().isEmpty () && getStatement () instanceof InsertSelectStatement ;
183
164
}
184
165
return true ;
185
166
}
186
167
187
- @ Override
188
- protected FetchClauseType getFetchClauseTypeForRowNumbering (QueryPart queryPart ) {
189
- final FetchClauseType fetchClauseType = super .getFetchClauseTypeForRowNumbering ( queryPart );
190
- final boolean hasOffset ;
191
- if ( queryPart .isRoot () && hasLimit () ) {
192
- hasOffset = getLimit ().getFirstRow () != null ;
193
- }
194
- else {
195
- hasOffset = queryPart .getOffsetClauseExpression () != null ;
196
- }
197
- if ( queryPart instanceof QuerySpec && !hasOffset && fetchClauseType == FetchClauseType .ROWS_ONLY ) {
198
- // We return null here, because in this particular case, we render a special rownum query
199
- // which can be seen in #emulateFetchOffsetWithWindowFunctions
200
- // Note that we also build upon this in #visitOrderBy
201
- return null ;
202
- }
203
- return fetchClauseType ;
204
- }
205
-
206
- @ Override
207
- protected void emulateFetchOffsetWithWindowFunctions (
208
- QueryPart queryPart ,
209
- Expression offsetExpression ,
210
- Expression fetchExpression ,
211
- FetchClauseType fetchClauseType ,
212
- boolean emulateFetchClause ) {
213
- if ( queryPart instanceof QuerySpec && offsetExpression == null && fetchClauseType == FetchClauseType .ROWS_ONLY ) {
214
- // Special case for Oracle to support locking along with simple max results paging
215
- final QuerySpec querySpec = (QuerySpec ) queryPart ;
216
- withRowNumbering (
217
- querySpec ,
218
- true , // we need select aliases to avoid ORA-00918: column ambiguously defined
219
- () -> {
220
- appendSql ( "select * from " );
221
- emulateFetchOffsetWithWindowFunctionsVisitQueryPart ( querySpec );
222
- appendSql ( " where rownum<=" );
223
- final Stack <Clause > clauseStack = getClauseStack ();
224
- clauseStack .push ( Clause .WHERE );
225
- try {
226
- fetchExpression .accept ( this );
227
-
228
- // We render the FOR UPDATE clause in the outer query
229
- clauseStack .pop ();
230
- clauseStack .push ( Clause .FOR_UPDATE );
231
- visitForUpdateClause ( querySpec );
232
- }
233
- finally {
234
- clauseStack .pop ();
235
- }
236
- }
237
- );
238
- }
239
- else {
240
- super .emulateFetchOffsetWithWindowFunctions (
241
- queryPart ,
242
- offsetExpression ,
243
- fetchExpression ,
244
- fetchClauseType ,
245
- emulateFetchClause
246
- );
247
- }
248
- }
249
-
250
168
@ Override
251
169
protected void visitOrderBy (List <SortSpecification > sortSpecifications ) {
252
170
// If we have a query part for row numbering, there is no need to render the order by clause
@@ -262,13 +180,49 @@ protected void visitOrderBy(List<SortSpecification> sortSpecifications) {
262
180
final QuerySpec querySpec = (QuerySpec ) queryPartForRowNumbering ;
263
181
if ( querySpec .getOffsetClauseExpression () == null
264
182
&& ( !querySpec .isRoot () || getOffsetParameter () == null ) ) {
265
- // When rendering `rownum` for Oracle, we need to render the order by clause still
266
- renderOrderBy ( true , sortSpecifications );
183
+ // When we enter here, we need to handle the special ROWNUM pagination
184
+ if ( hasGroupingOrDistinct ( querySpec ) || querySpec .getFromClause ().hasJoins () ) {
185
+ // When the query spec has joins, a group by, having or distinct clause,
186
+ // we just need to render the order by clause, because the query is wrapped
187
+ renderOrderBy ( true , sortSpecifications );
188
+ }
189
+ else {
190
+ // Otherwise we need to render the ROWNUM pagination predicate in here
191
+ final Predicate whereClauseRestrictions = querySpec .getWhereClauseRestrictions ();
192
+ if ( whereClauseRestrictions != null && !whereClauseRestrictions .isEmpty () ) {
193
+ appendSql ( " and " );
194
+ }
195
+ else {
196
+ appendSql ( " where " );
197
+ }
198
+ appendSql ( "rownum<=" );
199
+ final Stack <Clause > clauseStack = getClauseStack ();
200
+ clauseStack .push ( Clause .WHERE );
201
+ try {
202
+ if ( querySpec .isRoot () && hasLimit () ) {
203
+ getLimitParameter ().accept ( this );
204
+ }
205
+ else {
206
+ querySpec .getFetchClauseExpression ().accept ( this );
207
+ }
208
+ }
209
+ finally {
210
+ clauseStack .pop ();
211
+ }
212
+ renderOrderBy ( true , sortSpecifications );
213
+ visitForUpdateClause ( querySpec );
214
+ }
267
215
}
268
216
}
269
217
}
270
218
}
271
219
220
+ private boolean hasGroupingOrDistinct (QuerySpec querySpec ) {
221
+ return querySpec .getSelectClause ().isDistinct ()
222
+ || !querySpec .getGroupByClauseExpressions ().isEmpty ()
223
+ || querySpec .getHavingClauseRestrictions () != null ;
224
+ }
225
+
272
226
@ Override
273
227
protected void visitValuesList (List <Values > valuesList ) {
274
228
if ( valuesList .size () < 2 ) {
@@ -323,12 +277,142 @@ public void visitQueryGroup(QueryGroup queryGroup) {
323
277
324
278
@ Override
325
279
public void visitQuerySpec (QuerySpec querySpec ) {
280
+ final EntityIdentifierMapping identifierMappingForLockingWrapper = identifierMappingForLockingWrapper ( querySpec );
281
+ final Expression offsetExpression ;
282
+ final Expression fetchExpression ;
283
+ final FetchClauseType fetchClauseType ;
284
+ if ( querySpec .isRoot () && hasLimit () ) {
285
+ prepareLimitOffsetParameters ();
286
+ offsetExpression = getOffsetParameter ();
287
+ fetchExpression = getLimitParameter ();
288
+ fetchClauseType = FetchClauseType .ROWS_ONLY ;
289
+ }
290
+ else {
291
+ offsetExpression = querySpec .getOffsetClauseExpression ();
292
+ fetchExpression = querySpec .getFetchClauseExpression ();
293
+ fetchClauseType = querySpec .getFetchClauseType ();
294
+ }
326
295
if ( shouldEmulateFetchClause ( querySpec ) ) {
327
- emulateFetchOffsetWithWindowFunctions ( querySpec , true );
296
+ if ( identifierMappingForLockingWrapper == null ) {
297
+ emulateFetchOffsetWithWindowFunctions (
298
+ querySpec ,
299
+ offsetExpression ,
300
+ fetchExpression ,
301
+ fetchClauseType ,
302
+ true
303
+ );
304
+ }
305
+ else {
306
+ super .visitQuerySpec (
307
+ createLockingWrapper (
308
+ querySpec ,
309
+ offsetExpression ,
310
+ fetchExpression ,
311
+ fetchClauseType ,
312
+ identifierMappingForLockingWrapper
313
+ )
314
+ );
315
+ // Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
316
+ visitForUpdateClause ( querySpec );
317
+ }
328
318
}
329
319
else {
330
- super .visitQuerySpec ( querySpec );
320
+ if ( identifierMappingForLockingWrapper == null ) {
321
+ super .visitQuerySpec ( querySpec );
322
+ }
323
+ else {
324
+ super .visitQuerySpec (
325
+ createLockingWrapper (
326
+ querySpec ,
327
+ offsetExpression ,
328
+ fetchExpression ,
329
+ fetchClauseType ,
330
+ identifierMappingForLockingWrapper
331
+ )
332
+ );
333
+ // Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
334
+ visitForUpdateClause ( querySpec );
335
+ }
336
+ }
337
+ }
338
+
339
+ private QuerySpec createLockingWrapper (
340
+ QuerySpec querySpec ,
341
+ Expression offsetExpression ,
342
+ Expression fetchExpression ,
343
+ FetchClauseType fetchClauseType ,
344
+ EntityIdentifierMapping identifierMappingForLockingWrapper ) {
345
+
346
+ final TableGroup rootTableGroup = querySpec .getFromClause ().getRoots ().get ( 0 );
347
+ final List <ColumnReference > idColumnReferences = new ArrayList <>( identifierMappingForLockingWrapper .getJdbcTypeCount () );
348
+ identifierMappingForLockingWrapper .forEachSelectable (
349
+ 0 ,
350
+ (selectionIndex , selectableMapping ) -> {
351
+ idColumnReferences .add ( new ColumnReference ( rootTableGroup .getPrimaryTableReference (), selectableMapping ) );
352
+ }
353
+ );
354
+ final Expression idExpression ;
355
+ if ( identifierMappingForLockingWrapper instanceof EmbeddableValuedModelPart ) {
356
+ idExpression = new SqlTuple ( idColumnReferences , identifierMappingForLockingWrapper );
357
+ }
358
+ else {
359
+ idExpression = idColumnReferences .get ( 0 );
360
+ }
361
+ final QuerySpec subquery = new QuerySpec ( false , 1 );
362
+ for ( ColumnReference idColumnReference : idColumnReferences ) {
363
+ subquery .getSelectClause ().addSqlSelection ( new SqlSelectionImpl ( 0 , -1 , idColumnReference ) );
364
+ }
365
+ subquery .getFromClause ().addRoot ( rootTableGroup );
366
+ subquery .applyPredicate ( querySpec .getWhereClauseRestrictions () );
367
+ if ( querySpec .hasSortSpecifications () ) {
368
+ for ( SortSpecification sortSpecification : querySpec .getSortSpecifications () ) {
369
+ subquery .addSortSpecification ( sortSpecification );
370
+ }
331
371
}
372
+ subquery .setOffsetClauseExpression ( offsetExpression );
373
+ subquery .setFetchClauseExpression ( fetchExpression , fetchClauseType );
374
+
375
+ // Mark the query spec as non-root even if it might be the root, to avoid applying the pagination there
376
+ final QuerySpec lockingWrapper = new QuerySpec ( false , 1 );
377
+ lockingWrapper .getFromClause ().addRoot ( rootTableGroup );
378
+ for ( SqlSelection sqlSelection : querySpec .getSelectClause ().getSqlSelections () ) {
379
+ lockingWrapper .getSelectClause ().addSqlSelection ( sqlSelection );
380
+ }
381
+ lockingWrapper .applyPredicate ( new InSubQueryPredicate ( idExpression , subquery , false ) );
382
+ return lockingWrapper ;
383
+ }
384
+
385
+ private EntityIdentifierMapping identifierMappingForLockingWrapper (QuerySpec querySpec ) {
386
+ // We only need a locking wrapper for very simple queries
387
+ if ( canApplyLockingWrapper ( querySpec )
388
+ // There must be the need for locking in this query
389
+ && needsLocking ( querySpec )
390
+ // The query uses some sort of pagination which makes the wrapper necessary
391
+ && needsLockingWrapper ( querySpec )
392
+ // The query may not have a group by, having and distinct clause, or use aggregate functions,
393
+ // as these features will force the use of follow-on locking
394
+ && querySpec .getGroupByClauseExpressions ().isEmpty ()
395
+ && querySpec .getHavingClauseRestrictions () == null
396
+ && !querySpec .getSelectClause ().isDistinct ()
397
+ && !hasAggregateFunctions ( querySpec ) ) {
398
+ return ( (EntityMappingType ) querySpec .getFromClause ().getRoots ().get ( 0 ).getModelPart () ).getIdentifierMapping ();
399
+ }
400
+ return null ;
401
+ }
402
+
403
+ private boolean canApplyLockingWrapper (QuerySpec querySpec ) {
404
+ final FromClause fromClause ;
405
+ return querySpec .isRoot ()
406
+ // Must have a single root with no joins for an entity type
407
+ && ( fromClause = querySpec .getFromClause () ).getRoots ().size () == 1
408
+ && !fromClause .hasJoins ()
409
+ && fromClause .getRoots ().get ( 0 ).getModelPart () instanceof EntityMappingType ;
410
+ }
411
+
412
+ private boolean needsLockingWrapper (QuerySpec querySpec ) {
413
+ return querySpec .getFetchClauseType () != FetchClauseType .ROWS_ONLY
414
+ || hasOffset ( querySpec )
415
+ || hasLimit ( querySpec );
332
416
}
333
417
334
418
@ Override
0 commit comments