Implement EQL parser.
Implement support for EclipseLink Query Language (EQL), handling the various extensions it offers. Closes #3170 Original pull request: #3176
This commit is contained in:
committed by
Mark Paluch
parent
ae12c1cb05
commit
dde9e07c24
@@ -0,0 +1,897 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
grammar Eql;
|
||||
|
||||
@header {
|
||||
/**
|
||||
* Implementation of EclipseLink Query Language (EQL)
|
||||
* See:
|
||||
* * https://eclipse.dev/eclipselink/documentation/3.0/jpa/extensions/jpql.htm
|
||||
* * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.2
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
Parser rules
|
||||
*/
|
||||
|
||||
start
|
||||
: ql_statement EOF
|
||||
;
|
||||
|
||||
ql_statement
|
||||
: select_statement
|
||||
| update_statement
|
||||
| delete_statement
|
||||
;
|
||||
|
||||
select_statement
|
||||
: select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator select_statement)*
|
||||
;
|
||||
|
||||
setOperator
|
||||
: UNION ALL?
|
||||
| INTERSECT ALL?
|
||||
| EXCEPT ALL?
|
||||
;
|
||||
|
||||
update_statement
|
||||
: update_clause (where_clause)?
|
||||
;
|
||||
|
||||
delete_statement
|
||||
: delete_clause (where_clause)?
|
||||
;
|
||||
|
||||
from_clause
|
||||
: FROM identification_variable_declaration (',' identificationVariableDeclarationOrCollectionMemberDeclaration )*
|
||||
;
|
||||
|
||||
// This parser rule is needed to iterate over these two types from #from_clause
|
||||
identificationVariableDeclarationOrCollectionMemberDeclaration
|
||||
: identification_variable_declaration
|
||||
| collection_member_declaration
|
||||
| '(' subquery ')' identification_variable
|
||||
;
|
||||
|
||||
identification_variable_declaration
|
||||
: range_variable_declaration (join | fetch_join)*
|
||||
;
|
||||
|
||||
range_variable_declaration
|
||||
: (entity_name|function_invocation) AS? identification_variable
|
||||
;
|
||||
|
||||
join
|
||||
: join_spec join_association_path_expression AS? identification_variable? join_condition?
|
||||
;
|
||||
|
||||
fetch_join
|
||||
: join_spec FETCH join_association_path_expression AS? identification_variable? join_condition?
|
||||
;
|
||||
|
||||
join_spec
|
||||
: ((LEFT (OUTER)?) | INNER)? JOIN
|
||||
;
|
||||
|
||||
join_condition
|
||||
: ON conditional_expression
|
||||
;
|
||||
|
||||
join_association_path_expression
|
||||
: join_collection_valued_path_expression
|
||||
| join_single_valued_path_expression
|
||||
| TREAT '(' join_collection_valued_path_expression AS subtype ')'
|
||||
| TREAT '(' join_single_valued_path_expression AS subtype ')'
|
||||
;
|
||||
|
||||
join_collection_valued_path_expression
|
||||
: (identification_variable '.')? (single_valued_embeddable_object_field '.')* collection_valued_field
|
||||
;
|
||||
|
||||
join_single_valued_path_expression
|
||||
: (identification_variable '.')? (single_valued_embeddable_object_field '.')* single_valued_object_field
|
||||
;
|
||||
|
||||
collection_member_declaration
|
||||
: IN '(' collection_valued_path_expression ')' AS? identification_variable
|
||||
;
|
||||
|
||||
qualified_identification_variable
|
||||
: map_field_identification_variable
|
||||
| ENTRY '(' identification_variable ')'
|
||||
;
|
||||
|
||||
map_field_identification_variable
|
||||
: KEY '(' identification_variable ')'
|
||||
| VALUE '(' identification_variable ')'
|
||||
;
|
||||
|
||||
single_valued_path_expression
|
||||
: qualified_identification_variable
|
||||
| TREAT '(' qualified_identification_variable AS subtype ')'
|
||||
| state_field_path_expression
|
||||
| single_valued_object_path_expression
|
||||
;
|
||||
|
||||
general_identification_variable
|
||||
: identification_variable
|
||||
| map_field_identification_variable
|
||||
;
|
||||
|
||||
general_subpath
|
||||
: simple_subpath
|
||||
| treated_subpath ('.' single_valued_object_field)*
|
||||
;
|
||||
|
||||
simple_subpath
|
||||
: general_identification_variable
|
||||
| general_identification_variable ('.' single_valued_object_field)*
|
||||
;
|
||||
|
||||
treated_subpath
|
||||
: TREAT '(' general_subpath AS subtype ')'
|
||||
;
|
||||
|
||||
state_field_path_expression
|
||||
: general_subpath '.' state_field
|
||||
;
|
||||
|
||||
state_valued_path_expression
|
||||
: state_field_path_expression
|
||||
| general_identification_variable
|
||||
;
|
||||
|
||||
single_valued_object_path_expression
|
||||
: general_subpath '.' single_valued_object_field
|
||||
;
|
||||
|
||||
collection_valued_path_expression
|
||||
: general_subpath '.' collection_value_field // BNF at end of spec has a typo
|
||||
;
|
||||
|
||||
update_clause
|
||||
: UPDATE entity_name (AS? identification_variable)? SET update_item (',' update_item)*
|
||||
;
|
||||
|
||||
update_item
|
||||
: (identification_variable '.')? (single_valued_embeddable_object_field '.')* (state_field | single_valued_object_field) EQUAL new_value
|
||||
;
|
||||
|
||||
new_value
|
||||
: scalar_expression
|
||||
| simple_entity_expression
|
||||
| NULL
|
||||
;
|
||||
|
||||
delete_clause
|
||||
: DELETE FROM entity_name (AS? identification_variable)?
|
||||
;
|
||||
|
||||
select_clause
|
||||
: SELECT (DISTINCT)? select_item (',' select_item)*
|
||||
;
|
||||
|
||||
select_item
|
||||
: select_expression (AS? result_variable)?
|
||||
;
|
||||
|
||||
select_expression
|
||||
: single_valued_path_expression
|
||||
| scalar_expression
|
||||
| aggregate_expression
|
||||
| identification_variable
|
||||
| OBJECT '(' identification_variable ')'
|
||||
| constructor_expression
|
||||
;
|
||||
|
||||
constructor_expression
|
||||
: NEW constructor_name '(' constructor_item (',' constructor_item)* ')'
|
||||
;
|
||||
|
||||
constructor_item
|
||||
: single_valued_path_expression
|
||||
| scalar_expression
|
||||
| aggregate_expression
|
||||
| identification_variable
|
||||
;
|
||||
|
||||
aggregate_expression
|
||||
: (AVG | MAX | MIN | SUM) '(' (DISTINCT)? state_valued_path_expression ')'
|
||||
| COUNT '(' (DISTINCT)? (identification_variable | state_valued_path_expression | single_valued_object_path_expression) ')'
|
||||
| function_invocation
|
||||
;
|
||||
|
||||
where_clause
|
||||
: WHERE conditional_expression
|
||||
;
|
||||
|
||||
groupby_clause
|
||||
: GROUP BY groupby_item (',' groupby_item)*
|
||||
;
|
||||
|
||||
groupby_item
|
||||
: single_valued_path_expression
|
||||
| identification_variable
|
||||
| scalar_expression
|
||||
;
|
||||
|
||||
having_clause
|
||||
: HAVING conditional_expression
|
||||
;
|
||||
|
||||
orderby_clause
|
||||
: ORDER BY orderby_item (',' orderby_item)*
|
||||
;
|
||||
|
||||
// TODO Error in spec BNF, correctly shown elsewhere in spec.
|
||||
orderby_item
|
||||
: state_field_path_expression (ASC | DESC)? nullsPrecedence?
|
||||
| general_identification_variable (ASC | DESC)? nullsPrecedence?
|
||||
| result_variable (ASC | DESC)? nullsPrecedence?
|
||||
| string_expression (ASC | DESC)? nullsPrecedence?
|
||||
| scalar_expression (ASC | DESC)? nullsPrecedence?
|
||||
|
|
||||
;
|
||||
|
||||
nullsPrecedence
|
||||
: NULLS (FIRST | LAST)
|
||||
;
|
||||
|
||||
subquery
|
||||
: simple_select_clause subquery_from_clause (where_clause)? (groupby_clause)? (having_clause)?
|
||||
;
|
||||
|
||||
subquery_from_clause
|
||||
: FROM subselect_identification_variable_declaration (',' (subselect_identification_variable_declaration | collection_member_declaration))*
|
||||
;
|
||||
|
||||
subselect_identification_variable_declaration
|
||||
: identification_variable_declaration
|
||||
| derived_path_expression AS? identification_variable (join)*
|
||||
| derived_collection_member_declaration
|
||||
;
|
||||
|
||||
derived_path_expression
|
||||
: general_derived_path '.' single_valued_object_field
|
||||
| general_derived_path '.' collection_valued_field
|
||||
;
|
||||
|
||||
general_derived_path
|
||||
: simple_derived_path
|
||||
| treated_derived_path ('.' single_valued_object_field)*
|
||||
;
|
||||
|
||||
simple_derived_path
|
||||
: superquery_identification_variable ('.' single_valued_object_field)*
|
||||
;
|
||||
|
||||
treated_derived_path
|
||||
: TREAT '(' general_derived_path AS subtype ')'
|
||||
;
|
||||
|
||||
derived_collection_member_declaration
|
||||
: IN superquery_identification_variable '.' (single_valued_object_field '.')* collection_valued_field
|
||||
;
|
||||
|
||||
simple_select_clause
|
||||
: SELECT (DISTINCT)? simple_select_expression
|
||||
;
|
||||
|
||||
simple_select_expression
|
||||
: single_valued_path_expression
|
||||
| scalar_expression
|
||||
| aggregate_expression
|
||||
| identification_variable
|
||||
;
|
||||
|
||||
scalar_expression
|
||||
: arithmetic_expression
|
||||
| string_expression
|
||||
| enum_expression
|
||||
| datetime_expression
|
||||
| boolean_expression
|
||||
| case_expression
|
||||
| entity_type_expression
|
||||
;
|
||||
|
||||
conditional_expression
|
||||
: conditional_term
|
||||
| conditional_expression OR conditional_term
|
||||
;
|
||||
|
||||
conditional_term
|
||||
: conditional_factor
|
||||
| conditional_term AND conditional_factor
|
||||
;
|
||||
|
||||
conditional_factor
|
||||
: (NOT)? conditional_primary
|
||||
;
|
||||
|
||||
conditional_primary
|
||||
: simple_cond_expression
|
||||
| '(' conditional_expression ')'
|
||||
;
|
||||
|
||||
simple_cond_expression
|
||||
: comparison_expression
|
||||
| between_expression
|
||||
| in_expression
|
||||
| like_expression
|
||||
| null_comparison_expression
|
||||
| empty_collection_comparison_expression
|
||||
| collection_member_expression
|
||||
| exists_expression
|
||||
;
|
||||
|
||||
between_expression
|
||||
: arithmetic_expression (NOT)? BETWEEN arithmetic_expression AND arithmetic_expression
|
||||
| string_expression (NOT)? BETWEEN string_expression AND string_expression
|
||||
| datetime_expression (NOT)? BETWEEN datetime_expression AND datetime_expression
|
||||
;
|
||||
|
||||
in_expression
|
||||
: (state_valued_path_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter)
|
||||
;
|
||||
|
||||
in_item
|
||||
: literal
|
||||
| single_valued_input_parameter
|
||||
;
|
||||
|
||||
like_expression
|
||||
: string_expression (NOT)? LIKE pattern_value (ESCAPE escape_character)?
|
||||
;
|
||||
|
||||
null_comparison_expression
|
||||
: (single_valued_path_expression | input_parameter | nullif_expression) IS (NOT)? NULL
|
||||
;
|
||||
|
||||
empty_collection_comparison_expression
|
||||
: collection_valued_path_expression IS (NOT)? EMPTY
|
||||
;
|
||||
|
||||
collection_member_expression
|
||||
: entity_or_value_expression (NOT)? MEMBER (OF)? collection_valued_path_expression
|
||||
;
|
||||
|
||||
entity_or_value_expression
|
||||
: single_valued_object_path_expression
|
||||
| state_field_path_expression
|
||||
| simple_entity_or_value_expression
|
||||
;
|
||||
|
||||
simple_entity_or_value_expression
|
||||
: identification_variable
|
||||
| input_parameter
|
||||
| literal
|
||||
;
|
||||
|
||||
exists_expression
|
||||
: (NOT)? EXISTS '(' subquery ')'
|
||||
;
|
||||
|
||||
all_or_any_expression
|
||||
: (ALL | ANY | SOME) '(' subquery ')'
|
||||
;
|
||||
|
||||
comparison_expression
|
||||
: string_expression comparison_operator (string_expression | all_or_any_expression) #StringComparison
|
||||
| boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression) #BooleanComparison
|
||||
| boolean_expression #DirectBooleanCheck
|
||||
| enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression) #EnumComparison
|
||||
| datetime_expression comparison_operator (datetime_expression | all_or_any_expression) #DatetimeComparison
|
||||
| entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression) #EntityComparison
|
||||
| arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression) #ArithmeticComparison
|
||||
| entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression #EntityTypeComparison
|
||||
| string_expression REGEXP string_literal #RegexpComparison
|
||||
;
|
||||
|
||||
comparison_operator
|
||||
: op=EQUAL
|
||||
| op='>'
|
||||
| op='>='
|
||||
| op='<'
|
||||
| op='<='
|
||||
| op=NOT_EQUAL
|
||||
;
|
||||
|
||||
arithmetic_expression
|
||||
: arithmetic_term
|
||||
| arithmetic_expression op=('+' | '-') arithmetic_term
|
||||
;
|
||||
|
||||
arithmetic_term
|
||||
: arithmetic_factor
|
||||
| arithmetic_term op=('*' | '/') arithmetic_factor
|
||||
;
|
||||
|
||||
arithmetic_factor
|
||||
: op=('+' | '-')? arithmetic_primary
|
||||
;
|
||||
|
||||
arithmetic_primary
|
||||
: state_valued_path_expression
|
||||
| numeric_literal
|
||||
| '(' arithmetic_expression ')'
|
||||
| input_parameter
|
||||
| functions_returning_numerics
|
||||
| aggregate_expression
|
||||
| case_expression
|
||||
| cast_function
|
||||
| function_invocation
|
||||
| '(' subquery ')'
|
||||
;
|
||||
|
||||
string_expression
|
||||
: state_valued_path_expression
|
||||
| string_literal
|
||||
| input_parameter
|
||||
| functions_returning_strings
|
||||
| aggregate_expression
|
||||
| case_expression
|
||||
| function_invocation
|
||||
| '(' subquery ')'
|
||||
;
|
||||
|
||||
datetime_expression
|
||||
: state_valued_path_expression
|
||||
| input_parameter
|
||||
| functions_returning_datetime
|
||||
| aggregate_expression
|
||||
| case_expression
|
||||
| function_invocation
|
||||
| date_time_timestamp_literal
|
||||
| '(' subquery ')'
|
||||
;
|
||||
|
||||
boolean_expression
|
||||
: state_valued_path_expression
|
||||
| boolean_literal
|
||||
| input_parameter
|
||||
| case_expression
|
||||
| function_invocation
|
||||
| '(' subquery ')'
|
||||
;
|
||||
|
||||
enum_expression
|
||||
: state_valued_path_expression
|
||||
| enum_literal
|
||||
| input_parameter
|
||||
| case_expression
|
||||
| '(' subquery ')'
|
||||
;
|
||||
|
||||
entity_expression
|
||||
: single_valued_object_path_expression
|
||||
| simple_entity_expression
|
||||
;
|
||||
|
||||
simple_entity_expression
|
||||
: identification_variable
|
||||
| input_parameter
|
||||
;
|
||||
|
||||
entity_type_expression
|
||||
: type_discriminator
|
||||
| entity_type_literal
|
||||
| input_parameter
|
||||
;
|
||||
|
||||
type_discriminator
|
||||
: TYPE '(' (general_identification_variable | single_valued_object_path_expression | input_parameter) ')'
|
||||
;
|
||||
|
||||
functions_returning_numerics
|
||||
: LENGTH '(' string_expression ')'
|
||||
| LOCATE '(' string_expression ',' string_expression (',' arithmetic_expression)? ')'
|
||||
| ABS '(' arithmetic_expression ')'
|
||||
| CEILING '(' arithmetic_expression ')'
|
||||
| EXP '(' arithmetic_expression ')'
|
||||
| FLOOR '(' arithmetic_expression ')'
|
||||
| LN '(' arithmetic_expression ')'
|
||||
| SIGN '(' arithmetic_expression ')'
|
||||
| SQRT '(' arithmetic_expression ')'
|
||||
| MOD '(' arithmetic_expression '/' arithmetic_expression ')'
|
||||
| POWER '(' arithmetic_expression ',' arithmetic_expression ')'
|
||||
| ROUND '(' arithmetic_expression ',' arithmetic_expression ')'
|
||||
| SIZE '(' collection_valued_path_expression ')'
|
||||
| INDEX '(' identification_variable ')'
|
||||
| extract_datetime_field
|
||||
;
|
||||
|
||||
functions_returning_datetime
|
||||
: CURRENT_DATE
|
||||
| CURRENT_TIME
|
||||
| CURRENT_TIMESTAMP
|
||||
| LOCAL DATE
|
||||
| LOCAL TIME
|
||||
| LOCAL DATETIME
|
||||
| extract_datetime_part
|
||||
;
|
||||
|
||||
functions_returning_strings
|
||||
: CONCAT '(' string_expression ',' string_expression (',' string_expression)* ')'
|
||||
| SUBSTRING '(' string_expression ',' arithmetic_expression (',' arithmetic_expression)? ')'
|
||||
| TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')'
|
||||
| LOWER '(' string_expression ')'
|
||||
| UPPER '(' string_expression ')'
|
||||
;
|
||||
|
||||
trim_specification
|
||||
: LEADING
|
||||
| TRAILING
|
||||
| BOTH
|
||||
;
|
||||
|
||||
cast_function
|
||||
: CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
|
||||
;
|
||||
|
||||
function_invocation
|
||||
: (FUNCTION|identification_variable) '(' function_name (',' function_arg)* ')'
|
||||
;
|
||||
|
||||
extract_datetime_field
|
||||
: EXTRACT '(' datetime_field FROM datetime_expression ')'
|
||||
;
|
||||
|
||||
datetime_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
extract_datetime_part
|
||||
: EXTRACT '(' datetime_part FROM datetime_expression ')'
|
||||
;
|
||||
|
||||
datetime_part
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
function_arg
|
||||
: literal
|
||||
| state_valued_path_expression
|
||||
| input_parameter
|
||||
| scalar_expression
|
||||
;
|
||||
|
||||
case_expression
|
||||
: general_case_expression
|
||||
| simple_case_expression
|
||||
| coalesce_expression
|
||||
| nullif_expression
|
||||
;
|
||||
|
||||
general_case_expression
|
||||
: CASE when_clause (when_clause)* ELSE scalar_expression END
|
||||
;
|
||||
|
||||
when_clause
|
||||
: WHEN conditional_expression THEN scalar_expression
|
||||
;
|
||||
|
||||
simple_case_expression
|
||||
: CASE case_operand simple_when_clause (simple_when_clause)* ELSE scalar_expression END
|
||||
;
|
||||
|
||||
case_operand
|
||||
: state_valued_path_expression
|
||||
| type_discriminator
|
||||
;
|
||||
|
||||
simple_when_clause
|
||||
: WHEN scalar_expression THEN scalar_expression
|
||||
;
|
||||
|
||||
coalesce_expression
|
||||
: COALESCE '(' scalar_expression (',' scalar_expression)+ ')'
|
||||
;
|
||||
|
||||
nullif_expression
|
||||
: NULLIF '(' scalar_expression ',' scalar_expression ')'
|
||||
;
|
||||
|
||||
/*******************
|
||||
Gaps in the spec.
|
||||
*******************/
|
||||
|
||||
trim_character
|
||||
: CHARACTER
|
||||
| character_valued_input_parameter
|
||||
;
|
||||
|
||||
identification_variable
|
||||
: IDENTIFICATION_VARIABLE
|
||||
| f=(COUNT
|
||||
| DATE
|
||||
| FROM
|
||||
| INNER
|
||||
| KEY
|
||||
| LEFT
|
||||
| NEW
|
||||
| ORDER
|
||||
| OUTER
|
||||
| POWER
|
||||
| FLOOR
|
||||
| SIGN
|
||||
| TIME
|
||||
| TYPE
|
||||
| VALUE)
|
||||
;
|
||||
|
||||
constructor_name
|
||||
: state_field_path_expression
|
||||
;
|
||||
|
||||
literal
|
||||
: STRINGLITERAL
|
||||
| INTLITERAL
|
||||
| FLOATLITERAL
|
||||
| LONGLITERAL
|
||||
| boolean_literal
|
||||
| entity_type_literal
|
||||
;
|
||||
|
||||
input_parameter
|
||||
: '?' INTLITERAL
|
||||
| ':' identification_variable
|
||||
;
|
||||
|
||||
pattern_value
|
||||
: string_expression
|
||||
;
|
||||
|
||||
date_time_timestamp_literal
|
||||
: STRINGLITERAL
|
||||
| DATELITERAL
|
||||
| TIMELITERAL
|
||||
| TIMESTAMPLITERAL
|
||||
;
|
||||
|
||||
entity_type_literal
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
escape_character
|
||||
: CHARACTER
|
||||
| character_valued_input_parameter //
|
||||
;
|
||||
|
||||
numeric_literal
|
||||
: INTLITERAL
|
||||
| FLOATLITERAL
|
||||
| LONGLITERAL
|
||||
;
|
||||
|
||||
boolean_literal
|
||||
: TRUE
|
||||
| FALSE
|
||||
;
|
||||
|
||||
enum_literal
|
||||
: state_field_path_expression
|
||||
;
|
||||
|
||||
string_literal
|
||||
: CHARACTER
|
||||
| STRINGLITERAL
|
||||
;
|
||||
|
||||
single_valued_embeddable_object_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
subtype
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
collection_valued_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
single_valued_object_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
state_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
collection_value_field
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
entity_name
|
||||
: identification_variable ('.' identification_variable)* // Hibernate sometimes expands the entity name to FQDN when using named queries
|
||||
;
|
||||
|
||||
result_variable
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
superquery_identification_variable
|
||||
: identification_variable
|
||||
;
|
||||
|
||||
collection_valued_input_parameter
|
||||
: input_parameter
|
||||
;
|
||||
|
||||
single_valued_input_parameter
|
||||
: input_parameter
|
||||
;
|
||||
|
||||
function_name
|
||||
: string_literal
|
||||
;
|
||||
|
||||
character_valued_input_parameter
|
||||
: CHARACTER
|
||||
| input_parameter
|
||||
;
|
||||
|
||||
/*
|
||||
Lexer rules
|
||||
*/
|
||||
|
||||
|
||||
WS : [ \t\r\n] -> skip ;
|
||||
|
||||
// Build up case-insentive tokens
|
||||
|
||||
fragment A: 'a' | 'A';
|
||||
fragment B: 'b' | 'B';
|
||||
fragment C: 'c' | 'C';
|
||||
fragment D: 'd' | 'D';
|
||||
fragment E: 'e' | 'E';
|
||||
fragment F: 'f' | 'F';
|
||||
fragment G: 'g' | 'G';
|
||||
fragment H: 'h' | 'H';
|
||||
fragment I: 'i' | 'I';
|
||||
fragment J: 'j' | 'J';
|
||||
fragment K: 'k' | 'K';
|
||||
fragment L: 'l' | 'L';
|
||||
fragment M: 'm' | 'M';
|
||||
fragment N: 'n' | 'N';
|
||||
fragment O: 'o' | 'O';
|
||||
fragment P: 'p' | 'P';
|
||||
fragment Q: 'q' | 'Q';
|
||||
fragment R: 'r' | 'R';
|
||||
fragment S: 's' | 'S';
|
||||
fragment T: 't' | 'T';
|
||||
fragment U: 'u' | 'U';
|
||||
fragment V: 'v' | 'V';
|
||||
fragment W: 'w' | 'W';
|
||||
fragment X: 'x' | 'X';
|
||||
fragment Y: 'y' | 'Y';
|
||||
fragment Z: 'z' | 'Z';
|
||||
|
||||
// The following are reserved identifiers:
|
||||
|
||||
ABS : A B S;
|
||||
ALL : A L L;
|
||||
AND : A N D;
|
||||
ANY : A N Y;
|
||||
AS : A S;
|
||||
ASC : A S C;
|
||||
AVG : A V G;
|
||||
BETWEEN : B E T W E E N;
|
||||
BOTH : B O T H;
|
||||
BY : B Y;
|
||||
CASE : C A S E;
|
||||
CAST : C A S T;
|
||||
CEILING : C E I L I N G;
|
||||
COALESCE : C O A L E S C E;
|
||||
CONCAT : C O N C A T;
|
||||
COUNT : C O U N T;
|
||||
CURRENT_DATE : C U R R E N T '_' D A T E;
|
||||
CURRENT_TIME : C U R R E N T '_' T I M E;
|
||||
CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P;
|
||||
DATE : D A T E;
|
||||
DATETIME : D A T E T I M E ;
|
||||
DELETE : D E L E T E;
|
||||
DESC : D E S C;
|
||||
DISTINCT : D I S T I N C T;
|
||||
END : E N D;
|
||||
ELSE : E L S E;
|
||||
EMPTY : E M P T Y;
|
||||
ENTRY : E N T R Y;
|
||||
ESCAPE : E S C A P E;
|
||||
EXCEPT : E X C E P T;
|
||||
EXISTS : E X I S T S;
|
||||
EXP : E X P;
|
||||
EXTRACT : E X T R A C T;
|
||||
FALSE : F A L S E;
|
||||
FETCH : F E T C H;
|
||||
FIRST : F I R S T;
|
||||
FLOOR : F L O O R;
|
||||
FROM : F R O M;
|
||||
FUNCTION : F U N C T I O N;
|
||||
GROUP : G R O U P;
|
||||
HAVING : H A V I N G;
|
||||
IN : I N;
|
||||
INDEX : I N D E X;
|
||||
INNER : I N N E R;
|
||||
INTERSECT : I N T E R S E C T;
|
||||
IS : I S;
|
||||
JOIN : J O I N;
|
||||
KEY : K E Y;
|
||||
LAST : L A S T;
|
||||
LEADING : L E A D I N G;
|
||||
LEFT : L E F T;
|
||||
LENGTH : L E N G T H;
|
||||
LIKE : L I K E;
|
||||
LN : L N;
|
||||
LOCAL : L O C A L;
|
||||
LOCATE : L O C A T E;
|
||||
LOWER : L O W E R;
|
||||
MAX : M A X;
|
||||
MEMBER : M E M B E R;
|
||||
MIN : M I N;
|
||||
MOD : M O D;
|
||||
NEW : N E W;
|
||||
NOT : N O T;
|
||||
NULL : N U L L;
|
||||
NULLIF : N U L L I F;
|
||||
NULLS : N U L L S;
|
||||
OBJECT : O B J E C T;
|
||||
OF : O F;
|
||||
ON : O N;
|
||||
OR : O R;
|
||||
ORDER : O R D E R;
|
||||
OUTER : O U T E R;
|
||||
POWER : P O W E R;
|
||||
REGEXP : R E G E X P;
|
||||
ROUND : R O U N D;
|
||||
SELECT : S E L E C T;
|
||||
SET : S E T;
|
||||
SIGN : S I G N;
|
||||
SIZE : S I Z E;
|
||||
SOME : S O M E;
|
||||
SQRT : S Q R T;
|
||||
SUBSTRING : S U B S T R I N G;
|
||||
SUM : S U M;
|
||||
THEN : T H E N;
|
||||
TIME : T I M E;
|
||||
TRAILING : T R A I L I N G;
|
||||
TREAT : T R E A T;
|
||||
TRIM : T R I M;
|
||||
TRUE : T R U E;
|
||||
TYPE : T Y P E;
|
||||
UNION : U N I O N;
|
||||
UPDATE : U P D A T E;
|
||||
UPPER : U P P E R;
|
||||
VALUE : V A L U E;
|
||||
WHEN : W H E N;
|
||||
WHERE : W H E R E;
|
||||
|
||||
EQUAL : '=' ;
|
||||
NOT_EQUAL : '<>' | '!=' ;
|
||||
|
||||
|
||||
CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ;
|
||||
IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ;
|
||||
STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ;
|
||||
FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?;
|
||||
INTLITERAL : ('0' .. '9')+ ;
|
||||
LONGLITERAL : ('0' .. '9')+ L;
|
||||
DATELITERAL : '{' D STRINGLITERAL '}';
|
||||
TIMELITERAL : '{' T STRINGLITERAL '}';
|
||||
TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}';
|
||||
@@ -55,7 +55,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
* @author Greg Turnquist
|
||||
* @author Yuriy Tsarkov
|
||||
*/
|
||||
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
|
||||
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment, PresenceDetector {
|
||||
|
||||
/**
|
||||
* Hibernate persistence provider.
|
||||
@@ -110,6 +110,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
public String getCommentHintKey() {
|
||||
return "org.hibernate.comment";
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -203,6 +204,8 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
private final Iterable<String> entityManagerClassNames;
|
||||
private final Iterable<String> metamodelClassNames;
|
||||
|
||||
private boolean present;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistenceProvider}.
|
||||
*
|
||||
@@ -214,6 +217,13 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
|
||||
this.entityManagerClassNames = entityManagerClassNames;
|
||||
this.metamodelClassNames = metamodelClassNames;
|
||||
|
||||
this.present = false;
|
||||
entityManagerClassNames.forEach(entityManagerClassName -> {
|
||||
if (ClassUtils.isPresent(entityManagerClassName, PersistenceProvider.class.getClassLoader())) {
|
||||
this.present = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,6 +340,11 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer
|
||||
: value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return this.present;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the PersistenceProvider specific interface names.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
*/
|
||||
package org.springframework.data.jpa.provider;
|
||||
|
||||
/**
|
||||
* Define operations needed to detect the presence of a given JPA provider.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.2
|
||||
*/
|
||||
interface PresenceDetector {
|
||||
boolean isPresent();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Implements the {@code EQL} parsing operations of a {@link JpaQueryParserSupport} using the ANTLR-generated
|
||||
* {@link EqlParser} and {@link EqlQueryTransformer}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.2
|
||||
*/
|
||||
class EqlQueryParser extends JpaQueryParserSupport {
|
||||
|
||||
EqlQueryParser(String query) {
|
||||
super(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to parse a EQL query. Will throw a {@link BadJpqlGrammarException} if the query is invalid.
|
||||
*
|
||||
* @param query
|
||||
* @return a parsed query, ready for postprocessing
|
||||
*/
|
||||
public static ParserRuleContext parseQuery(String query) {
|
||||
|
||||
EqlLexer lexer = new EqlLexer(CharStreams.fromString(query));
|
||||
EqlParser parser = new EqlParser(new CommonTokenStream(lexer));
|
||||
|
||||
configureParser(query, lexer, parser);
|
||||
|
||||
return parser.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the query using {@link #parseQuery(String)}.
|
||||
*
|
||||
* @return a parsed query
|
||||
*/
|
||||
@Override
|
||||
protected ParserRuleContext parse(String query) {
|
||||
return parseQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the {@link EqlQueryTransformer} to transform the original query into a query with the {@link Sort} applied.
|
||||
*
|
||||
* @param parsedQuery
|
||||
* @param sort can be {@literal null}
|
||||
* @return list of {@link JpaQueryParsingToken}s
|
||||
*/
|
||||
@Override
|
||||
protected List<JpaQueryParsingToken> applySort(ParserRuleContext parsedQuery, Sort sort) {
|
||||
return new EqlQueryTransformer(sort).visit(parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the {@link EqlQueryTransformer} to transform the original query into a count query.
|
||||
*
|
||||
* @param parsedQuery
|
||||
* @param countProjection
|
||||
* @return list of {@link JpaQueryParsingToken}s
|
||||
*/
|
||||
@Override
|
||||
protected List<JpaQueryParsingToken> doCreateCountQuery(ParserRuleContext parsedQuery,
|
||||
@Nullable String countProjection) {
|
||||
return new EqlQueryTransformer(true, countProjection).visit(parsedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the parsed query through {@link EqlQueryTransformer} to find the primary FROM clause's alias.
|
||||
*
|
||||
* @param parsedQuery
|
||||
* @return can be {@literal null}
|
||||
*/
|
||||
@Override
|
||||
protected String doFindAlias(ParserRuleContext parsedQuery) {
|
||||
|
||||
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
|
||||
transformVisitor.visit(parsedQuery);
|
||||
return transformVisitor.getAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link EqlQueryTransformer} to find the projection of the query.
|
||||
*
|
||||
* @param parsedQuery
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected List<JpaQueryParsingToken> doFindProjection(ParserRuleContext parsedQuery) {
|
||||
|
||||
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
|
||||
transformVisitor.visit(parsedQuery);
|
||||
return transformVisitor.getProjection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link EqlQueryTransformer} to detect if the query uses a {@code new com.example.Dto()} DTO constructor in the
|
||||
* primary select clause.
|
||||
*
|
||||
* @param parsedQuery
|
||||
* @return Guaranteed to be {@literal true} or {@literal false}.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doCheckForConstructor(ParserRuleContext parsedQuery) {
|
||||
|
||||
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
|
||||
transformVisitor.visit(parsedQuery);
|
||||
return transformVisitor.hasConstructorExpression();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.2
|
||||
*/
|
||||
class EqlQueryTransformer extends EqlQueryRenderer {
|
||||
|
||||
// TODO: Separate input from result parameters, encapsulation...
|
||||
private final Sort sort;
|
||||
private final boolean countQuery;
|
||||
|
||||
private final @Nullable String countProjection;
|
||||
|
||||
private @Nullable String primaryFromAlias = null;
|
||||
|
||||
private List<JpaQueryParsingToken> projection = Collections.emptyList();
|
||||
private boolean projectionProcessed;
|
||||
|
||||
private boolean hasConstructorExpression = false;
|
||||
|
||||
private JpaQueryTransformerSupport transformerSupport;
|
||||
|
||||
EqlQueryTransformer() {
|
||||
this(Sort.unsorted(), false, null);
|
||||
}
|
||||
|
||||
EqlQueryTransformer(Sort sort) {
|
||||
this(sort, false, null);
|
||||
}
|
||||
|
||||
EqlQueryTransformer(boolean countQuery, @Nullable String countProjection) {
|
||||
this(Sort.unsorted(), countQuery, countProjection);
|
||||
}
|
||||
|
||||
private EqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String countProjection) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.countQuery = countQuery;
|
||||
this.countProjection = countProjection;
|
||||
this.transformerSupport = new JpaQueryTransformerSupport();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAlias() {
|
||||
return this.primaryFromAlias;
|
||||
}
|
||||
|
||||
public List<JpaQueryParsingToken> getProjection() {
|
||||
return this.projection;
|
||||
}
|
||||
|
||||
public boolean hasConstructorExpression() {
|
||||
return this.hasConstructorExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitSelect_statement(EqlParser.Select_statementContext ctx) {
|
||||
|
||||
List<JpaQueryParsingToken> tokens = newArrayList();
|
||||
|
||||
tokens.addAll(visit(ctx.select_clause()));
|
||||
tokens.addAll(visit(ctx.from_clause()));
|
||||
|
||||
if (ctx.where_clause() != null) {
|
||||
tokens.addAll(visit(ctx.where_clause()));
|
||||
}
|
||||
|
||||
if (ctx.groupby_clause() != null) {
|
||||
tokens.addAll(visit(ctx.groupby_clause()));
|
||||
}
|
||||
|
||||
if (ctx.having_clause() != null) {
|
||||
tokens.addAll(visit(ctx.having_clause()));
|
||||
}
|
||||
|
||||
if (!countQuery) {
|
||||
|
||||
if (ctx.orderby_clause() != null) {
|
||||
tokens.addAll(visit(ctx.orderby_clause()));
|
||||
}
|
||||
|
||||
if (sort.isSorted()) {
|
||||
|
||||
if (ctx.orderby_clause() != null) {
|
||||
|
||||
NOSPACE(tokens);
|
||||
tokens.add(TOKEN_COMMA);
|
||||
} else {
|
||||
|
||||
SPACE(tokens);
|
||||
tokens.add(TOKEN_ORDER_BY);
|
||||
}
|
||||
|
||||
tokens.addAll(transformerSupport.generateOrderByArguments(primaryFromAlias, sort));
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitSelect_clause(EqlParser.Select_clauseContext ctx) {
|
||||
|
||||
List<JpaQueryParsingToken> tokens = newArrayList();
|
||||
|
||||
tokens.add(new JpaQueryParsingToken(ctx.SELECT()));
|
||||
|
||||
if (countQuery) {
|
||||
tokens.add(TOKEN_COUNT_FUNC);
|
||||
}
|
||||
|
||||
if (ctx.DISTINCT() != null) {
|
||||
tokens.add(new JpaQueryParsingToken(ctx.DISTINCT()));
|
||||
}
|
||||
|
||||
List<JpaQueryParsingToken> selectItemTokens = newArrayList();
|
||||
|
||||
ctx.select_item().forEach(selectItemContext -> {
|
||||
selectItemTokens.addAll(visit(selectItemContext));
|
||||
NOSPACE(selectItemTokens);
|
||||
selectItemTokens.add(TOKEN_COMMA);
|
||||
});
|
||||
CLIP(selectItemTokens);
|
||||
SPACE(selectItemTokens);
|
||||
|
||||
if (countQuery) {
|
||||
|
||||
if (countProjection != null) {
|
||||
tokens.add(new JpaQueryParsingToken(countProjection));
|
||||
} else {
|
||||
|
||||
if (ctx.DISTINCT() != null) {
|
||||
|
||||
if (selectItemTokens.stream().anyMatch(jpqlToken -> jpqlToken.getToken().contains("new"))) {
|
||||
// constructor
|
||||
tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias));
|
||||
} else {
|
||||
// keep all the select items to distinct against
|
||||
tokens.addAll(selectItemTokens);
|
||||
}
|
||||
} else {
|
||||
tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias));
|
||||
}
|
||||
}
|
||||
|
||||
NOSPACE(tokens);
|
||||
tokens.add(TOKEN_CLOSE_PAREN);
|
||||
} else {
|
||||
tokens.addAll(selectItemTokens);
|
||||
}
|
||||
|
||||
if (!projectionProcessed) {
|
||||
projection = selectItemTokens;
|
||||
projectionProcessed = true;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitSelect_item(EqlParser.Select_itemContext ctx) {
|
||||
|
||||
List<JpaQueryParsingToken> tokens = super.visitSelect_item(ctx);
|
||||
|
||||
if (ctx.result_variable() != null) {
|
||||
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) {
|
||||
|
||||
List<JpaQueryParsingToken> tokens = newArrayList();
|
||||
|
||||
tokens.addAll(visit(ctx.entity_name()));
|
||||
|
||||
if (ctx.AS() != null) {
|
||||
tokens.add(new JpaQueryParsingToken(ctx.AS()));
|
||||
}
|
||||
|
||||
tokens.addAll(visit(ctx.identification_variable()));
|
||||
|
||||
if (primaryFromAlias == null) {
|
||||
primaryFromAlias = tokens.get(tokens.size() - 1).getToken();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitJoin(EqlParser.JoinContext ctx) {
|
||||
|
||||
List<JpaQueryParsingToken> tokens = super.visitJoin(ctx);
|
||||
|
||||
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JpaQueryParsingToken> visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) {
|
||||
|
||||
hasConstructorExpression = true;
|
||||
|
||||
return super.visitConstructor_expression(ctx);
|
||||
}
|
||||
|
||||
private static <T> ArrayList<T> newArrayList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
|
||||
* @since 3.1
|
||||
* @see JpqlQueryParser
|
||||
* @see HqlQueryParser
|
||||
* @see EqlQueryParser
|
||||
*/
|
||||
class JpaQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
@@ -73,6 +74,20 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
return new JpaQueryEnhancer(query, new HqlQueryParser(query.getQueryString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link JpaQueryParserSupport} for {@link DeclaredQuery} using EQL grammar.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using EQL.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static JpaQueryEnhancer forEql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
return new JpaQueryEnhancer(query, new EqlQueryParser(query.getQueryString()));
|
||||
}
|
||||
|
||||
protected JpaQueryParserSupport getQueryParsingStrategy() {
|
||||
return queryParser;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,12 @@ class JpaQueryParsingToken {
|
||||
|
||||
public static final JpaQueryParsingToken TOKEN_MATERIALIZED = new JpaQueryParsingToken("materialized");
|
||||
|
||||
public static final JpaQueryParsingToken TOKEN_NULLS = new JpaQueryParsingToken("NULLS");
|
||||
|
||||
public static final JpaQueryParsingToken TOKEN_FIRST = new JpaQueryParsingToken("FIRST");
|
||||
|
||||
public static final JpaQueryParsingToken TOKEN_LAST = new JpaQueryParsingToken("LAST");
|
||||
|
||||
/**
|
||||
* The text value of the token.
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -34,18 +35,20 @@ public final class QueryEnhancerFactory {
|
||||
private static final boolean jSqlParserPresent = ClassUtils.isPresent("net.sf.jsqlparser.parser.JSqlParser",
|
||||
QueryEnhancerFactory.class.getClassLoader());
|
||||
|
||||
private static final boolean hibernatePresent = ClassUtils.isPresent("org.hibernate.query.TypedParameterValue",
|
||||
QueryEnhancerFactory.class.getClassLoader());
|
||||
|
||||
static {
|
||||
|
||||
if (jSqlParserPresent) {
|
||||
LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used");
|
||||
}
|
||||
|
||||
if (hibernatePresent) {
|
||||
if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
LOG.info("EclipseLink is in classpath; If applicable, EQL parser will be used.");
|
||||
}
|
||||
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
LOG.info("Hibernate is in classpath; If applicable, HQL parser will be used.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private QueryEnhancerFactory() {}
|
||||
@@ -70,7 +73,13 @@ public final class QueryEnhancerFactory {
|
||||
return new DefaultQueryEnhancer(query);
|
||||
}
|
||||
|
||||
return hibernatePresent ? JpaQueryEnhancer.forHql(query) : JpaQueryEnhancer.forJpql(query);
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
return JpaQueryEnhancer.forHql(query);
|
||||
} else if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
return JpaQueryEnhancer.forEql(query);
|
||||
} else {
|
||||
return JpaQueryEnhancer.forJpql(query);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests built around examples of EQL found in the EclipseLink's docs at
|
||||
* https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL<br/>
|
||||
* <br/>
|
||||
* IMPORTANT: Purely verifies the parser without any transformations.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class EqlComplianceTests {
|
||||
|
||||
/**
|
||||
* Parse the query using {@link EqlParser} then run it through the query-preserving {@link EqlQueryRenderer}.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
private static String parseWithoutChanges(String query) {
|
||||
|
||||
EqlLexer lexer = new EqlLexer(CharStreams.fromString(query));
|
||||
EqlParser parser = new EqlParser(new CommonTokenStream(lexer));
|
||||
|
||||
parser.addErrorListener(new BadJpqlGrammarErrorListener(query));
|
||||
|
||||
EqlParser.StartContext parsedQuery = parser.start();
|
||||
|
||||
return render(new EqlQueryRenderer().visit(parsedQuery));
|
||||
}
|
||||
|
||||
private void assertQuery(String query) {
|
||||
|
||||
String slimmedDownQuery = reduceWhitespace(query);
|
||||
assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery);
|
||||
}
|
||||
|
||||
private String reduceWhitespace(String original) {
|
||||
|
||||
return original //
|
||||
.replaceAll("[ \\t\\n]{1,}", " ") //
|
||||
.trim();
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectQueries() {
|
||||
|
||||
assertQuery("Select e FROM Employee e WHERE e.salary > 100000");
|
||||
assertQuery("Select e FROM Employee e WHERE e.id = :id");
|
||||
assertQuery("Select MAX(e.salary) FROM Employee e");
|
||||
assertQuery("Select e.firstName FROM Employee e");
|
||||
assertQuery("Select e.firstName, e.lastName FROM Employee e");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectClause() {
|
||||
|
||||
assertQuery("SELECT COUNT(e) FROM Employee e");
|
||||
assertQuery("SELECT MAX(e.salary) FROM Employee e");
|
||||
assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClause() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e");
|
||||
assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address");
|
||||
assertQuery("SELECT e FROM com.acme.Employee e");
|
||||
}
|
||||
|
||||
@Test
|
||||
void join() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city");
|
||||
assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinFetch() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e JOIN FETCH e.address");
|
||||
assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoin() {
|
||||
assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city");
|
||||
}
|
||||
|
||||
@Test
|
||||
void on() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e LEFT JOIN e.address ON a.city = :city");
|
||||
assertQuery("SELECT e FROM Employee e LEFT JOIN MailingAddress a ON e.address = a.address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subselectsInFromClause() {
|
||||
assertQuery(
|
||||
"SELECT e, c.city FROM Employee e, (SELECT DISTINCT a.city FROM Address a) c WHERE e.address.city = c.city");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByClause() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.lastName ASC, e.firstName ASC"); // Typo in EQL document
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY UPPER(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e LEFT JOIN e.manager m ORDER BY m.lastName NULLS FIRST");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupByClause() {
|
||||
|
||||
assertQuery("SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city");
|
||||
assertQuery("SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city ORDER BY AVG(e.salary)");
|
||||
assertQuery("SELECT e, COUNT(p) FROM Employee e LEFT JOIN e.projects p GROUP BY e");
|
||||
}
|
||||
|
||||
@Test
|
||||
void havingClause() {
|
||||
assertQuery(
|
||||
"SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city HAVING AVG(e.salary) > 100000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void union() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city1
|
||||
UNION SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city2
|
||||
""");
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1
|
||||
INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2
|
||||
""");
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e
|
||||
EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whereClause() {
|
||||
// TBD
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateQueries() {
|
||||
assertQuery("UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 50000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteQueries() {
|
||||
assertQuery("DELETE FROM Employee e WHERE e.department IS NULL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void literals() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.id = 1234");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L");
|
||||
assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F");
|
||||
assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.active = TRUE");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00.000000001'}");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE");
|
||||
assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionsInSelect() {
|
||||
|
||||
assertQuery("SELECT e.salary - 1000 FROM Employee e");
|
||||
assertQuery("SELECT e.salary + 1000 FROM Employee e");
|
||||
assertQuery("SELECT e.salary*2 FROM Employee e");
|
||||
assertQuery("SELECT e.salary*2.0 FROM Employee e");
|
||||
assertQuery("SELECT e.salary/2 FROM Employee e");
|
||||
assertQuery("SELECT e.salary/2.0 FROM Employee e");
|
||||
assertQuery("SELECT ABS(e.salary - e.manager.salary) FROM Employee e");
|
||||
assertQuery(
|
||||
"select e from Employee e where case e.firstName when 'Bob' then 'Robert' when 'Jill' then 'Gillian' else '' end = 'Robert'");
|
||||
assertQuery(
|
||||
"select case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end from Employee e where e.firstName = 'Bob' or e.firstName = 'Jill'");
|
||||
assertQuery(
|
||||
"select e from Employee e where case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end = 'Robert'");
|
||||
assertQuery("SELECT COALESCE(e.salary, 0) FROM Employee e");
|
||||
assertQuery("SELECT CONCAT(e.firstName, ' ', e.lastName) FROM Employee e");
|
||||
assertQuery("SELECT e.name, CURRENT_DATE FROM Employee e");
|
||||
assertQuery("SELECT e.name, CURRENT_TIME FROM Employee e");
|
||||
assertQuery("SELECT e.name, CURRENT_TIMESTAMP FROM Employee e");
|
||||
assertQuery("SELECT LENGTH(e.lastName) FROM Employee e");
|
||||
assertQuery("SELECT LOWER(e.lastName) FROM Employee e");
|
||||
assertQuery("SELECT MOD(e.hoursWorked / 8) FROM Employee e");
|
||||
assertQuery("SELECT NULLIF(e.salary, 0) FROM Employee e");
|
||||
assertQuery("SELECT SQRT(o.RESULT) FROM Output o");
|
||||
assertQuery("SELECT SUBSTRING(e.lastName, 0, 2) FROM Employee e");
|
||||
assertQuery(
|
||||
"SELECT TRIM(TRAILING FROM e.lastName), TRIM(e.lastName), TRIM(LEADING '-' FROM e.lastName) FROM Employee e");
|
||||
assertQuery("SELECT UPPER(e.lastName) FROM Employee e");
|
||||
assertQuery("SELECT CAST(e.salary NUMERIC(10, 2)) FROM Employee e");
|
||||
assertQuery("SELECT EXTRACT(YEAR FROM e.startDate) FROM Employee e");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.lastName REGEXP '^Dr.*'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.lastName REGEXP '^Dr\\.*'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionsInWhere() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary - 1000 > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary + 1000 > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary*2 > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary*2.0 > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary/2 > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.salary/2.0 > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE ABS(e.salary - e.manager.salary) > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE COALESCE(e.salary, 0) > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE CURRENT_DATE > CURRENT_TIME");
|
||||
assertQuery("SELECT e FROM Employee e WHERE CURRENT_TIME > CURRENT_TIMESTAMP");
|
||||
assertQuery("SELECT e FROM Employee e WHERE LENGTH(e.lastName) > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE LOWER(e.lastName) = 'bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked / 8) > 0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE NULLIF(e.salary, 0) is null");
|
||||
assertQuery("SELECT e FROM Employee e WHERE SQRT(o.RESULT) > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE SUBSTRING(e.lastName, 0, 2) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE TRIM(TRAILING FROM e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE TRIM(e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE TRIM(LEADING '-' FROM e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE UPPER(e.lastName) = 'BILBO'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e WHERE EXTRACT(YEAR FROM e.startDate) = '2023'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionsInOrderBy() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary - 1000");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary + 1000");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary*2");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary*2.0");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary/2");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY e.salary/2.0");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY ABS(e.salary - e.manager.salary)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY COALESCE(e.salary, 0)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY CONCAT(e.firstName, ' ', e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY CURRENT_DATE");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY CURRENT_TIME");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY CURRENT_TIMESTAMP");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY LENGTH(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY LOWER(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY MOD(e.hoursWorked / 8)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY NULLIF(e.salary, 0)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY SQRT(o.RESULT)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY SUBSTRING(e.lastName, 0, 2)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY TRIM(TRAILING FROM e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY TRIM(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY TRIM(LEADING '-' FROM e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY UPPER(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY CAST(e.salary NUMERIC(10, 2))");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY EXTRACT(YEAR FROM e.startDate)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionsInGroupBy() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary - 1000");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary + 1000");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary*2");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary*2.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary/2");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary/2.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY ABS(e.salary - e.manager.salary)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY COALESCE(e.salary, 0)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY CONCAT(e.firstName, ' ', e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY CURRENT_DATE");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY CURRENT_TIME");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY CURRENT_TIMESTAMP");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY LENGTH(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY LOWER(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY MOD(e.hoursWorked / 8)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY NULLIF(e.salary, 0)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY SQRT(o.RESULT)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY SUBSTRING(e.lastName, 0, 2)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY TRIM(TRAILING FROM e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY TRIM(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY TRIM(LEADING '-' FROM e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY UPPER(e.lastName)");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY CAST(e.salary NUMERIC(10, 2))");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY EXTRACT(YEAR FROM e.startDate)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionsInHaving() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary - 1000 > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary + 1000 > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary*2 > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary*2.0 > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary/2 > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary/2.0 > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING ABS(e.salary - e.manager.salary) > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING COALESCE(e.salary, 0) > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CURRENT_DATE > CURRENT_TIME");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CURRENT_TIME > CURRENT_TIMESTAMP");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING LENGTH(e.lastName) > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING LOWER(e.lastName) = 'bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING MOD(e.hoursWorked / 8) > 0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING NULLIF(e.salary, 0) is null");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING SQRT(o.RESULT) > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING SUBSTRING(e.lastName, 0, 2) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING TRIM(TRAILING FROM e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING TRIM(e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING TRIM(LEADING '-' FROM e.lastName) = 'Bilbo'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING UPPER(e.lastName) = 'BILBO'");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CAST(e.salary NUMERIC(10, 2)) > 0.0");
|
||||
assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING EXTRACT(YEAR FROM e.startDate) = '2023'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void specialOperators() {
|
||||
|
||||
assertQuery("SELECT toDo FROM Employee e JOIN e.toDoList toDo WHERE INDEX(toDo) = 1");
|
||||
assertQuery("SELECT p FROM Employee e JOIN e.priorities p WHERE KEY(p) = 'high'");
|
||||
assertQuery("SELECT e FROM Employee e WHERE SIZE(e.managedEmployees) < 2");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY");
|
||||
assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities");
|
||||
assertQuery("SELECT p FROM Project p WHERE TYPE(p) = LargeProject");
|
||||
|
||||
/**
|
||||
* NOTE: The following query has been altered to properly align with EclipseLink test code despite NOT matching
|
||||
* their ref docs. See https://github.com/eclipse-ee4j/eclipselink/issues/1949 for more details.
|
||||
*/
|
||||
assertQuery("SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) p WHERE p.budget > 1000000");
|
||||
|
||||
assertQuery("SELECT p FROM Phone p WHERE FUNCTION('TO_NUMBER', p.areaCode) > 613");
|
||||
}
|
||||
|
||||
@Test
|
||||
void eclipseLinkSpecialOperators() {
|
||||
|
||||
assertQuery("SELECT p FROM Phone p WHERE FUNC('TO_NUMBER', e.areaCode) > 613");
|
||||
assertQuery("SELECT FUNC('YEAR', e.startDate) AS YEAR, COUNT(e) FROM Employee e GROUP BY YEAR");
|
||||
assertQuery(
|
||||
"SELECT a FROM Asset a, Geography geo WHERE geo.id = :id AND a.id IN :id_list AND FUNC('ST_INTERSECTS', a.geometry, geo.geometry) = 'TRUE'");
|
||||
assertQuery(
|
||||
"SELECT s FROM SimpleSpatial s WHERE FUNC('MDSYS.SDO_RELATE', s.jGeometry, :otherGeometry, :params) = 'TRUE' ORDER BY s.id ASC");
|
||||
assertQuery("SELECT e FROM Employee e WHERE OPERATOR('ExtractXml', e.resume, '@years-experience') > 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sql() {
|
||||
|
||||
assertQuery("SELECT p FROM Phone p WHERE SQL('CAST(? AS CHAR(3))', e.areaCode) = '613'");
|
||||
assertQuery("SELECT SQL('EXTRACT(YEAR FROM ?)', e.startDate) AS YEAR, COUNT(e) FROM Employee e GROUP BY YEAR");
|
||||
assertQuery("SELECT e FROM Employee e ORDER BY SQL('? NULLS FIRST', e.startDate)");
|
||||
assertQuery("SELECT e FROM Employee e WHERE e.startDate = SQL('(SELECT SYSDATE FROM DUAL)')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void column() {
|
||||
|
||||
assertQuery("SELECT e FROM Employee e WHERE COLUMN('MANAGER_ID', e) = :id");
|
||||
assertQuery("SELECT e FROM Employee e WHERE COLUMN('ROWID', e) = :id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void table() {
|
||||
assertQuery(
|
||||
"SELECT e, a.LAST_UPDATE_USER FROM Employee e, TABLE('AUDIT') a WHERE a.TABLE = 'EMPLOYEE' AND a.ROWID = COLUMN('ROWID', e)");
|
||||
}
|
||||
|
||||
@Test // GH-3175
|
||||
void coalesceFunctions() {
|
||||
|
||||
assertQuery("SELECT b FROM Bundle b WHERE coalesce(b.deleted, false) AND b.latestImport = true");
|
||||
assertQuery("SELECT b FROM Bundle b WHERE NOT coalesce(b.deleted, false) AND b.latestImport = true");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
/**
|
||||
* TCK Tests for {@link EqlQueryParser} mixed into {@link JpaQueryEnhancer}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class EqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
@Override
|
||||
QueryEnhancer createQueryEnhancer(DeclaredQuery query) {
|
||||
|
||||
assumeThat(query.isNativeQuery()).isFalse();
|
||||
|
||||
return JpaQueryEnhancer.forEql(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ParameterizedTest // GH-2773
|
||||
@MethodSource("jpqlCountQueries")
|
||||
void shouldDeriveJpqlCountQuery(String query, String expected) {
|
||||
|
||||
assumeThat(query).as("EqlParser replaces the column name with alias name for count queries") //
|
||||
.doesNotContain("SELECT name FROM table_name some_alias");
|
||||
|
||||
assumeThat(query).as("EqlParser does not support simple JPQL syntax") //
|
||||
.doesNotStartWithIgnoringCase("FROM");
|
||||
|
||||
assumeThat(expected).as("EqlParser does turn 'select a.b' into 'select count(a.b)'") //
|
||||
.doesNotContain("select count(a.b");
|
||||
|
||||
super.shouldDeriveJpqlCountQuery(query, expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,995 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests built around examples of EQL found in the JPA spec
|
||||
* https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc<br/>
|
||||
* <br/>
|
||||
* IMPORTANT: Purely verifies the parser without any transformations.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class EqlQueryRendererTests {
|
||||
|
||||
private static final String SPEC_FAULT = "Disabled due to spec fault> ";
|
||||
|
||||
/**
|
||||
* Parse the query using {@link EqlParser} then run it through the query-preserving {@link EqlQueryRenderer}.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
private static String parseWithoutChanges(String query) {
|
||||
|
||||
EqlLexer lexer = new EqlLexer(CharStreams.fromString(query));
|
||||
EqlParser parser = new EqlParser(new CommonTokenStream(lexer));
|
||||
|
||||
parser.addErrorListener(new BadJpqlGrammarErrorListener(query));
|
||||
|
||||
EqlParser.StartContext parsedQuery = parser.start();
|
||||
|
||||
return render(new EqlQueryRenderer().visit(parsedQuery));
|
||||
}
|
||||
|
||||
private void assertQuery(String query) {
|
||||
|
||||
String slimmedDownQuery = reduceWhitespace(query);
|
||||
assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery);
|
||||
}
|
||||
|
||||
private String reduceWhitespace(String original) {
|
||||
|
||||
return original //
|
||||
.replaceAll("[ \\t\\n]{1,}", " ") //
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example
|
||||
*/
|
||||
@Test
|
||||
void joinExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order AS o JOIN o.lineItems AS l
|
||||
WHERE l.shipped = FALSE
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#identification-variables
|
||||
*/
|
||||
@Test
|
||||
void joinExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l JOIN l.product p
|
||||
WHERE p.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#range-variable-declarations
|
||||
*/
|
||||
@Test
|
||||
void rangeVariableDeclarations() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o1
|
||||
FROM Order o1, Order o2
|
||||
WHERE o1.quantity > o2.quantity AND
|
||||
o2.customer.lastname = 'Smith' AND
|
||||
o2.customer.firstname = 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT i.name, VALUE(p)
|
||||
FROM Item i JOIN i.photos p
|
||||
WHERE KEY(p) LIKE '%egret'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT i.name, p
|
||||
FROM Item i JOIN i.photos p
|
||||
WHERE KEY(p) LIKE '%egret'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample3() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo.phones p
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample4() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
|
||||
WHERE e.contactInfo.address.zipcode = '95054'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathExpressionSyntaxExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT l.product
|
||||
FROM Order AS o JOIN o.lineItems l
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c FROM Customer c, Employee e WHERE c.hatsize = e.shoesize
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsInnerExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsInExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doubleJoinExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
|
||||
WHERE c.address.zipcode = '95054'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinOnExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
ON p.status = 'inStock'
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinWhereExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
WHERE p.status = 'inStock'
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinFetchExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT d
|
||||
FROM Department d LEFT JOIN FETCH d.employees
|
||||
WHERE d.deptno = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectionMemberExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.product.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectionMemberInExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o, IN(o.lineItems) l
|
||||
WHERE l.product.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order AS o JOIN o.lineItems l JOIN l.product p
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT b.name, b.ISBN
|
||||
FROM Order o JOIN TREAT(o.product AS Book) b
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp
|
||||
WHERE lp.budget > 1000
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #fromClauseDowncastingExample3fixed()
|
||||
*/
|
||||
@Test
|
||||
@Disabled(SPEC_FAULT + "Use double-quotes when it should be using single-quotes for a string literal")
|
||||
void fromClauseDowncastingExample3_SPEC_BUG() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e JOIN e.projects p
|
||||
WHERE TREAT(p AS LargeProject).budget > 1000
|
||||
OR TREAT(p AS SmallProject).name LIKE 'Persist%'
|
||||
OR p.description LIKE "cost overrun"
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample3fixed() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e JOIN e.projects p
|
||||
WHERE TREAT(p AS LargeProject).budget > 1000
|
||||
OR TREAT(p AS SmallProject).name LIKE 'Persist%'
|
||||
OR p.description LIKE 'cost overrun'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample4() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e FROM Employee e
|
||||
WHERE TREAT(e AS Exempt).vacationDays > 10
|
||||
OR TREAT(e AS Contractor).hours > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathExpressionsNamedParametersExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE c.status = :stat
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void betweenExpressionsExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT t
|
||||
FROM CreditCard c JOIN c.transactionHistory t
|
||||
WHERE c.holder.name = 'John Doe' AND INDEX(t) BETWEEN 0 AND 9
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void memberOfExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p
|
||||
FROM Person p
|
||||
WHERE 'Joe' MEMBER OF p.nicknames
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsSubSelectExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT emp
|
||||
FROM Employee emp
|
||||
WHERE EXISTS (SELECT spouseEmp
|
||||
FROM Employee spouseEmp
|
||||
WHERE spouseEmp = emp.spouse)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT emp
|
||||
FROM Employee emp
|
||||
WHERE emp.salary > ALL (SELECT m.salary
|
||||
FROM Manager m
|
||||
WHERE m.department = emp.department)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsSubSelectExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT emp
|
||||
FROM Employee emp
|
||||
WHERE EXISTS (SELECT spouseEmp
|
||||
FROM Employee spouseEmp
|
||||
WHERE spouseEmp = emp.spouse)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subselectNumericComparisonExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE (SELECT AVG(o.price) FROM c.orders o) > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subselectNumericComparisonExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT goodCustomer
|
||||
FROM Customer goodCustomer
|
||||
WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed)/2.0 FROM Customer c)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexExample() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT w.name
|
||||
FROM Course c JOIN c.studentWaitlist w
|
||||
WHERE c.name = 'Calculus'
|
||||
AND INDEX(w) = 0
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #functionInvocationExampleWithCorrection()
|
||||
*/
|
||||
@Test
|
||||
@Disabled(SPEC_FAULT + "FUNCTION calls needs a comparator")
|
||||
void functionInvocationExample_SPEC_BUG() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionInvocationExampleWithCorrection() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateCaseExample1() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.salary =
|
||||
CASE WHEN e.rating = 1 THEN e.salary*1.1
|
||||
WHEN e.rating = 2 THEN e.salary*1.05
|
||||
ELSE e.salary*1.01
|
||||
END
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateCaseExample2() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.salary =
|
||||
CASE e.rating WHEN 1 THEN e.salary*1.1
|
||||
WHEN 2 THEN e.salary*1.05
|
||||
ELSE e.salary*1.01
|
||||
END
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectCaseExample1() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e.name,
|
||||
CASE TYPE(e) WHEN Exempt THEN 'Exempt'
|
||||
WHEN Contractor THEN 'Contractor'
|
||||
WHEN Intern THEN 'Intern'
|
||||
ELSE 'NonExempt'
|
||||
END
|
||||
FROM Employee e
|
||||
WHERE e.dept.name = 'Engineering'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectCaseExample2() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e.name,
|
||||
f.name,
|
||||
CONCAT(CASE WHEN f.annualMiles > 50000 THEN 'Platinum '
|
||||
WHEN f.annualMiles > 25000 THEN 'Gold '
|
||||
ELSE ''
|
||||
END,
|
||||
'Frequent Flyer')
|
||||
FROM Employee e JOIN e.frequentFlierPlan f
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inClauseWithTypeLiteralsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN (Exempt, Contractor)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inClauseWithParametersShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN (:empType1, :empType2)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inClauseWithSingleParameterShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN :empTypes
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void notEqualsForTypeShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT TYPE(e)
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) <> Exempt
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void havingWithInClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.status, AVG(c.filledOrderCount), COUNT(c)
|
||||
FROM Customer c
|
||||
GROUP BY c.status
|
||||
HAVING c.status IN (1, 2)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void havingClauseWithComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.country, COUNT(c)
|
||||
FROM Customer c
|
||||
GROUP BY c.country
|
||||
HAVING COUNT(c) > 30
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void havingClauseWithAnotherComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c, COUNT(o)
|
||||
FROM Customer c JOIN c.orders o
|
||||
GROUP BY c
|
||||
HAVING COUNT(o) >= 5
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whereClauseWithComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.id, c.status
|
||||
FROM Customer c JOIN c.orders o
|
||||
WHERE o.count > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyValueFunctionsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT v.location.street, KEY(i).title, VALUE(i)
|
||||
FROM VideoStore v JOIN v.videoInventory i
|
||||
WHERE v.location.zipcode = '94301' AND VALUE(i) > 0
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseWithAsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.lineItems FROM Order AS o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countFunctionWithAsClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c, COUNT(l) AS itemCount
|
||||
FROM Customer c JOIN c.Orders o JOIN o.lineItems l
|
||||
WHERE c.address.state = 'CA'
|
||||
GROUP BY c
|
||||
ORDER BY itemCount
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void objectConstructionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count)
|
||||
FROM Customer c JOIN c.orders o
|
||||
WHERE o.count > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithAsClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e.address AS addr
|
||||
FROM Employee e
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT AVG(o.quantity) FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sumFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT SUM(l.price)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(o) FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countFunctionOnSubElementShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(l.price)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void equivalentCountFunctionShouldAlsoWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(l)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John' AND l.price IS NOT NULL
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByBasedOnSelectClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
ORDER BY o.quantity DESC, o.totalcost
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByThatMatchesSelectClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.quantity, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
ORDER BY o.quantity, a.zipcode
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByThatMatchesAllSelectAliasesShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA' AND a.county = 'Santa Clara'
|
||||
ORDER BY o.quantity, taxedCost, a.zipcode
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderByThatMatchesSelectFunctionAliasShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT AVG(o.quantity) as q, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
GROUP BY a.zipcode
|
||||
ORDER BY q DESC
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This query is specifically dubbed illegal in the spec. However, it's not due to a grammar failure but instead
|
||||
* for semantic reasons. Our parser does NOT check if the ORDER BY clause matches the SELECT or not. Hence, this is
|
||||
* left to the JPA provider.
|
||||
*/
|
||||
@Test
|
||||
void orderByClauseThatIsNotReflectedInTheSelectClause() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p.product_name
|
||||
FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
ORDER BY p.price
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This query is specifically dubbed illegal in the spec. It may actually be failing for a different reason.
|
||||
*/
|
||||
@Test
|
||||
void orderByClauseThatIsNotReflectedInTheSelectClauseButAlsoHasAnInClauseInTheFromClause() {
|
||||
|
||||
assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> {
|
||||
assertQuery("""
|
||||
SELECT p.product_name
|
||||
FROM Order o, IN(o.lineItems) l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
ORDER BY o.quantity
|
||||
""");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleDeleteShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
DELETE
|
||||
FROM Customer c
|
||||
WHERE c.status = 'inactive'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteWithMoreComplexCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
DELETE
|
||||
FROM Customer c
|
||||
WHERE c.status = 'inactive'
|
||||
AND c.orders IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleUpdateShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Customer c
|
||||
SET c.status = 'outstanding'
|
||||
WHERE c.balance < 10000
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void moreComplexUpdateShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.address.building = 22
|
||||
WHERE e.address.building = 14
|
||||
AND e.address.city = 'Santa Clara'
|
||||
AND e.project = 'Jakarta EE'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleSelectShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithWhereClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.shippingAddress.state = 'CA'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithDistinctSubElementShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o.shippingAddress.state
|
||||
FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithSimpleDistinctShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithIsNotEmptyCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS NOT EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithIsEmptyCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllPendingOrders() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.shipped = FALSE
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllOrdersWhereShippingAddressDoesNotMatchBillingAddress() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE
|
||||
NOT (o.shippingAddress.state = o.billingAddress.state AND
|
||||
o.shippingAddress.city = o.billingAddress.city AND
|
||||
o.shippingAddress.street = o.billingAddress.street)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void simplerVersionOfShippingAddressNotMatchingBillingAddress() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.shippingAddress <> o.billingAddress
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOrdersThatHaveProductNamedByAParameter() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.product.name = ?1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // GH-2982
|
||||
void floorShouldBeValidEntityName() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT f
|
||||
FROM Floor f
|
||||
WHERE f.name = :name
|
||||
""");
|
||||
|
||||
assertQuery("""
|
||||
SELECT r
|
||||
FROM Room r
|
||||
JOIN r.floor f
|
||||
WHERE f.name = :name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // GH-2994
|
||||
void queryWithSignShouldWork() {
|
||||
assertQuery("select t.sign from TestEntity t");
|
||||
}
|
||||
|
||||
@Test // GH-3028
|
||||
void queryWithValueShouldWork() {
|
||||
assertQuery("select t.value from TestEntity t");
|
||||
}
|
||||
|
||||
@Test // GH-3062, GH-3056
|
||||
void typeShouldBeAValidParameter() {
|
||||
|
||||
assertQuery("select e from Employee e where e.type = :_type");
|
||||
assertQuery("select te from TestEntity te where te.type = :type");
|
||||
}
|
||||
|
||||
@Test // GH-3061
|
||||
void alternateNotEqualsOperatorShouldWork() {
|
||||
assertQuery("select e from Employee e where e.firstName != :name");
|
||||
}
|
||||
|
||||
@Test // GH-3092
|
||||
void dateAndFromShouldBeValidNames() {
|
||||
assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date BETWEEN :from AND :to");
|
||||
}
|
||||
|
||||
@Test // GH-3092
|
||||
void timeShouldBeAValidParameterName() {
|
||||
assertQuery("""
|
||||
UPDATE Lock L
|
||||
SET L.isLocked = TRUE, L.forceUnlockTime = :forceUnlockTime
|
||||
WHERE L.isLocked = FALSE OR L.forceUnlockTime < :time
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // GH-3128
|
||||
void newShouldBeLegalAsPartOfAStateFieldPathExpression() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT j
|
||||
FROM AgentUpdateTask j
|
||||
WHERE j.creationTimestamp < :date
|
||||
AND (j.status = com.ca.apm.acc.configserver.core.domain.jobs.AgentUpdateTaskStatus.NEW
|
||||
OR
|
||||
j.status = com.ca.apm.acc.configserver.core.domain.jobs.AgentUpdateTaskStatus.STARTED
|
||||
OR
|
||||
j.status = com.ca.apm.acc.configserver.core.domain.jobs.AgentUpdateTaskStatus.QUEUED)
|
||||
ORDER BY j.id
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // GH-3143
|
||||
void powerShouldBeLegalInAQuery() {
|
||||
assertQuery("select e.power.id from MyEntity e");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,783 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.JpaSort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Verify that EQL queries are properly transformed through the {@link JpaQueryEnhancer} and the {@link EqlQueryParser}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class EqlQueryTransformerTests {
|
||||
|
||||
private static final String QUERY = "select u from User u";
|
||||
private static final String SIMPLE_QUERY = "select u from User u";
|
||||
private static final String COUNT_QUERY = "select count(u) from User u";
|
||||
private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?1";
|
||||
|
||||
@Test
|
||||
void applyingSortShouldIntroduceOrderByCriteriaWhereNoneExists() {
|
||||
|
||||
// given
|
||||
var original = "SELECT e FROM Employee e where e.name = :name";
|
||||
var sort = Sort.by("first_name", "last_name");
|
||||
|
||||
// when
|
||||
var results = createQueryFor(original, sort);
|
||||
|
||||
// then
|
||||
assertThat(original).doesNotContainIgnoringCase("order by");
|
||||
assertThat(results).contains("order by e.first_name asc, e.last_name asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyingSortShouldCreateAdditionalOrderByCriteria() {
|
||||
|
||||
// given
|
||||
var original = "SELECT e FROM Employee e where e.name = :name ORDER BY e.role, e.hire_date";
|
||||
var sort = Sort.by("first_name", "last_name");
|
||||
|
||||
// when
|
||||
var results = createQueryFor(original, sort);
|
||||
|
||||
// then
|
||||
assertThat(results).contains("ORDER BY e.role, e.hire_date, e.first_name asc, e.last_name asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyCountToSimpleQuery() {
|
||||
|
||||
// given
|
||||
var original = "SELECT e FROM Employee e where e.name = :name";
|
||||
|
||||
// when
|
||||
var results = createCountQueryFor(original);
|
||||
|
||||
// then
|
||||
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyCountToMoreComplexQuery() {
|
||||
|
||||
// given
|
||||
var original = "SELECT e FROM Employee e where e.name = :name ORDER BY e.modified_date";
|
||||
|
||||
// when
|
||||
var results = createCountQueryFor(original);
|
||||
|
||||
// then
|
||||
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyCountToAlreadySorteQuery() {
|
||||
|
||||
// given
|
||||
var original = "SELECT e FROM Employee e where e.name = :name ORDER BY e.modified_date";
|
||||
|
||||
// when
|
||||
var results = createCountQueryFor(original);
|
||||
|
||||
// then
|
||||
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleAliasesShouldBeGathered() {
|
||||
|
||||
// given
|
||||
var original = "select e from Employee e join e.manager m";
|
||||
|
||||
// when
|
||||
var results = createQueryFor(original, Sort.unsorted());
|
||||
|
||||
// then
|
||||
assertThat(results).isEqualTo("select e from Employee e join e.manager m");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryCorrectly() {
|
||||
assertCountQuery(QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueriesCorrectlyForCapitalLetterEQL() {
|
||||
|
||||
assertCountQuery("select u FROM User u WHERE u.foo.bar = ?1", "select count(u) FROM User u WHERE u.foo.bar = ?1");
|
||||
assertCountQuery("SELECT u FROM User u where u.foo.bar = ?1", "SELECT count(u) FROM User u where u.foo.bar = ?1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForDistinctQueries() {
|
||||
|
||||
assertCountQuery("select distinct u from User u where u.foo = ?1",
|
||||
"select count(distinct u) from User u where u.foo = ?1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForConstructorQueries() {
|
||||
|
||||
assertCountQuery("select distinct new com.example.User(u.name) from User u where u.foo = ?1",
|
||||
"select count(distinct u) from User u where u.foo = ?1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForJoins() {
|
||||
|
||||
assertCountQuery("select distinct new com.User(u.name) from User u left outer join u.roles r WHERE r = ?1",
|
||||
"select count(distinct u) from User u left outer join u.roles r WHERE r = ?1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForQueriesWithSubSelects() {
|
||||
|
||||
assertCountQuery("select u from User u left outer join u.roles r where r in (select r from Role r)",
|
||||
"select count(u) from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForAliasesCorrectly() {
|
||||
assertCountQuery("select u from User as u", "select count(u) from User as u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsShortJpaSyntax() {
|
||||
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test // GH-2260
|
||||
void detectsAliasCorrectly() {
|
||||
|
||||
assertThat(alias(QUERY)).isEqualTo("u");
|
||||
assertThat(alias(SIMPLE_QUERY)).isEqualTo("u");
|
||||
assertThat(alias(COUNT_QUERY)).isEqualTo("u");
|
||||
assertThat(alias(QUERY_WITH_AS)).isEqualTo("u");
|
||||
assertThat(alias("SELECT u FROM USER U")).isEqualTo("U");
|
||||
assertThat(alias("select u from User u")).isEqualTo("u");
|
||||
assertThat(alias("select new com.acme.UserDetails(u.id, u.name) from User u")).isEqualTo("u");
|
||||
assertThat(alias("select u from T05User u")).isEqualTo("u");
|
||||
assertThat(alias("select u from User u where not exists (select m from User m where m = u.manager) "))
|
||||
.isEqualTo("u");
|
||||
assertThat(alias("select u from User u where not exists (select u2 from User u2)")).isEqualTo("u");
|
||||
assertThat(alias(
|
||||
"select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))"))
|
||||
.isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // GH-2557
|
||||
void applySortingAccountsForNewlinesInSubselect() {
|
||||
|
||||
Sort sort = Sort.by(Sort.Order.desc("age"));
|
||||
|
||||
assertThat(newParser("""
|
||||
select u
|
||||
from user u
|
||||
where exists (select u2
|
||||
from user u2
|
||||
)
|
||||
""").applySorting(sort)).isEqualToIgnoringWhitespace("""
|
||||
select u
|
||||
from user u
|
||||
where exists (select u2
|
||||
from user u2
|
||||
)
|
||||
order by u.age desc""");
|
||||
}
|
||||
|
||||
@Test // GH-2563
|
||||
void aliasDetectionProperlyHandlesNewlinesInSubselects() {
|
||||
|
||||
assertThat(alias("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE EXISTS( SELECT 1
|
||||
FROM Vehicle vehicle
|
||||
WHERE vehicle.vehicleOrderId = o.id
|
||||
AND LOWER(COALESCE(vehicle.make, '')) LIKE :query)
|
||||
""")).isEqualTo("o");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252, GH-664, GH-1066, GH-2960
|
||||
void doesNotPrefixOrderReferenceIfOuterJoinAliasDetected() {
|
||||
|
||||
String query = "select p from Person p left join p.address address";
|
||||
Sort sort = Sort.by("address.city");
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by address.city asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void extendsExistingOrderByClausesCorrectly() {
|
||||
|
||||
String query = "select p from Person p order by p.lastname asc";
|
||||
Sort sort = Sort.by("firstname");
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by p.lastname asc, p.firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appliesIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
String query = "select p from Person p";
|
||||
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appendsIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
String query = "select p from Person p order by p.lastname asc";
|
||||
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
|
||||
|
||||
assertThat(createQueryFor(query, sort))
|
||||
.isEqualTo("select p from Person p order by p.lastname asc, lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-342
|
||||
void usesReturnedVariableInCountProjectionIfSet() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-343
|
||||
void projectsCountQueriesForQueriesWithSubselects() {
|
||||
|
||||
// given
|
||||
var original = "select o from Foo o where cb.id in (select b from Bar b)";
|
||||
|
||||
// when
|
||||
var results = createQueryFor(original, Sort.by("first_name", "last_name"));
|
||||
|
||||
// then
|
||||
assertThat(results).isEqualTo(
|
||||
"select o from Foo o where cb.id in (select b from Bar b) order by o.first_name asc, o.last_name asc");
|
||||
|
||||
assertCountQuery("select o from Foo o where cb.id in (select b from Bar b)",
|
||||
"select count(o) from Foo o where cb.id in (select b from Bar b)");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-148
|
||||
void doesNotPrefixSortsIfFunction() {
|
||||
|
||||
Sort sort = Sort.by("sum(foo)");
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> createQueryFor("select p from Person p", sort));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-377
|
||||
void removesOrderByInGeneratedCountQueryFromOriginalQueryIfPresent() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-375
|
||||
void findsExistingOrderByIndependentOfCase() {
|
||||
|
||||
Sort sort = Sort.by("lastname");
|
||||
String query = createQueryFor("select p from Person p ORDER BY p.firstname", sort);
|
||||
assertThat(query).endsWith("ORDER BY p.firstname, p.lastname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-409
|
||||
void createsCountQueryForNestedReferenceCorrectly() {
|
||||
assertCountQuery("select a.b from A a", "select count(a) from A a");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-420
|
||||
void createsCountQueryForScalarSelects() {
|
||||
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-456
|
||||
void createCountQueryFromTheGivenCountProjection() {
|
||||
|
||||
assertThat(createCountQueryFor("select p.lastname,p.firstname from Person p", "p.lastname"))
|
||||
.isEqualTo("select count(p.lastname) from Person p");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-736
|
||||
void supportsNonAsciiCharactersInEntityNames() {
|
||||
assertThat(createCountQueryFor("select u from Usèr u")).isEqualTo("select count(u) from Usèr u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-798
|
||||
void detectsAliasInQueryContainingLineBreaks() {
|
||||
assertThat(alias("select \n u \n from \n User \nu")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionInDistinctQuery() {
|
||||
assertThat(hasConstructorExpression("select distinct new com.example.Foo(b.name) from Bar b")).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsComplexConstructorExpression() {
|
||||
|
||||
//
|
||||
assertThat(hasConstructorExpression(
|
||||
"""
|
||||
select new foo.bar.Foo(ip.id, ip.name, sum(lp.amount))
|
||||
from Bar lp join lp.investmentProduct ip
|
||||
where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId group by ip.id, ip.name, lp.accountId
|
||||
order by ip.name ASC"""))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionWithLineBreaks() {
|
||||
assertThat(hasConstructorExpression("select new foo.bar.FooBar(\na.id) from DtoA a ")).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotAllowWhitespaceInSort() {
|
||||
|
||||
Sort sort = Sort.by("case when foo then bar");
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> createQueryFor("select p from Person p", sort));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixUnsafeJpaSortFunctionCalls() {
|
||||
|
||||
JpaSort sort = JpaSort.unsafe("sum(foo)");
|
||||
assertThat(createQueryFor("select p from Person p", sort)).endsWith("order by sum(foo) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixMultipleAliasedFunctionCalls() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice, SUM(m.stocks) AS sumStocks FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice", "sumStocks");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by avgPrice asc, sumStocks asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixSingleAliasedFunctionCalls() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void prefixesSingleNonAliasedFunctionCallRelatedSortProperty() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("someOtherProperty");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by m.someOtherProperty asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainsAliasedFunctionForDifferentProperty() {
|
||||
|
||||
String query = "SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("name", "avgPrice");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by m.name asc, avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleNumericParameters() {
|
||||
|
||||
String query = "SELECT SUBSTRING(m.name, 2, 5) AS trimmedName FROM Magazine m";
|
||||
Sort sort = Sort.by("trimmedName");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by trimmedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleStringParameters() {
|
||||
|
||||
String query = "SELECT CONCAT(m.name, 'foo') AS extendedName FROM Magazine m";
|
||||
Sort sort = Sort.by("extendedName");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by extendedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixAliasedFunctionCallNameWithUnderscores() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avg_price FROM Magazine m";
|
||||
Sort sort = Sort.by("avg_price");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by avg_price asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixAliasedFunctionCallNameWithDots() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS m.avg FROM Magazine m";
|
||||
Sort sort = Sort.by("m.avg");
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> createQueryFor(query, sort));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970, GH-2863
|
||||
void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpaces() {
|
||||
|
||||
String query = "SELECT AVG( m.price ) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
assertThat(createQueryFor(query, sort)).endsWith("order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1506
|
||||
void detectsAliasWithGroupAndOrderBy() {
|
||||
|
||||
assertThat(alias("select * from User group by name")).isNull();
|
||||
assertThat(alias("select * from User order by name")).isNull();
|
||||
assertThat(alias("select u from User u group by name")).isEqualTo("u");
|
||||
assertThat(alias("select u from User u order by name")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1500
|
||||
void createCountQuerySupportsWhitespaceCharacters() {
|
||||
|
||||
assertThat(createCountQueryFor("""
|
||||
select user from User user
|
||||
where user.age = 18
|
||||
order by user.name
|
||||
""")).isEqualToIgnoringWhitespace("""
|
||||
select count(user) from User user
|
||||
where user.age = 18
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreaksInSelectClause() {
|
||||
|
||||
assertThat(createCountQueryFor("""
|
||||
select user.age,
|
||||
user.name
|
||||
from User user
|
||||
where user.age = 18
|
||||
order
|
||||
by
|
||||
user.name
|
||||
""")).isEqualToIgnoringWhitespace("""
|
||||
select count(user) from User user
|
||||
where user.age = 18
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFieldAliases() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("authorName");
|
||||
|
||||
String fullQuery = createQueryFor(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by authorName asc");
|
||||
}
|
||||
|
||||
@Test // GH-2280
|
||||
void appliesOrderingCorrectlyForFieldAliasWithIgnoreCase() {
|
||||
|
||||
String query = "SELECT customer.id as id, customer.name as name FROM CustomerEntity customer";
|
||||
Sort sort = Sort.by(Sort.Order.by("name").ignoreCase());
|
||||
|
||||
String fullQuery = createQueryFor(query, sort);
|
||||
|
||||
assertThat(fullQuery).isEqualTo(
|
||||
"SELECT customer.id as id, customer.name as name FROM CustomerEntity customer order by lower(name) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFunctionAliases() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("title");
|
||||
|
||||
String fullQuery = createQueryFor(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by title asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForSimpleField() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("price");
|
||||
|
||||
String fullQuery = createQueryFor(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by m.price asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreakRightAfterDistinct() {
|
||||
|
||||
assertThat(createCountQueryFor("""
|
||||
select
|
||||
distinct
|
||||
user.age,
|
||||
user.name
|
||||
from
|
||||
User
|
||||
user""")).isEqualTo(createCountQueryFor("""
|
||||
select
|
||||
distinct user.age,
|
||||
user.name
|
||||
from
|
||||
User
|
||||
user"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasWithGroupAndOrderByWithLineBreaks() {
|
||||
|
||||
assertThat(alias("select * from User group\nby name")).isNull();
|
||||
assertThat(alias("select * from User order\nby name")).isNull();
|
||||
assertThat(alias("select u from User u group\nby name")).isEqualTo("u");
|
||||
assertThat(alias("select u from User u order\nby name")).isEqualTo("u");
|
||||
assertThat(alias("select u from User\nu\norder \n by name")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1679
|
||||
void findProjectionClauseWithDistinct() {
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
softly.assertThat(projection("select a,b,c from Entity x")).isEqualTo("a, b, c");
|
||||
softly.assertThat(projection("select a, b, c from Entity x")).isEqualTo("a, b, c");
|
||||
softly.assertThat(projection("select distinct a, b, c from Entity x")).isEqualTo("a, b, c");
|
||||
softly.assertThat(projection("select DISTINCT a, b, c from Entity x")).isEqualTo("a, b, c");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithSubselect() {
|
||||
|
||||
// This is not a required behavior, in fact the opposite is,
|
||||
// but it documents a current limitation.
|
||||
// to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser.
|
||||
assertThat(projection("select * from (select x from y)")).isNotEqualTo("*");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithIncludedFrom() {
|
||||
assertThat(projection("select x, frommage, y from Element t")).isEqualTo("x, frommage, y");
|
||||
}
|
||||
|
||||
@Test // GH-2341
|
||||
void countProjectionDistrinctQueryIncludesNewLineAfterFromAndBeforeJoin() {
|
||||
|
||||
String originalQuery = "SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key";
|
||||
assertCountQuery(originalQuery,
|
||||
"SELECT count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key");
|
||||
}
|
||||
|
||||
@Test // GH-2341
|
||||
void countProjectionDistinctQueryIncludesNewLineAfterEntity() {
|
||||
|
||||
String originalQuery = "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key";
|
||||
assertCountQuery(originalQuery,
|
||||
"SELECT count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key");
|
||||
}
|
||||
|
||||
@Test // GH-2341
|
||||
void countProjectionDistinctQueryIncludesNewLineAfterEntityAndBeforeWhere() {
|
||||
|
||||
String originalQuery = "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799";
|
||||
assertCountQuery(originalQuery,
|
||||
"SELECT count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799");
|
||||
}
|
||||
|
||||
@Test // GH-2393
|
||||
void createCountQueryStartsWithWhitespace() {
|
||||
|
||||
assertThat(createCountQueryFor(" \nselect u from User u where u.age > :age"))
|
||||
.isEqualTo("select count(u) from User u where u.age > :age");
|
||||
|
||||
assertThat(createCountQueryFor(" \nselect u from User u where u.age > :age"))
|
||||
.isEqualTo("select count(u) from User u where u.age > :age");
|
||||
}
|
||||
|
||||
@Test // GH-2260
|
||||
void applySortingAccountsForNativeWindowFunction() {
|
||||
|
||||
Sort sort = Sort.by(Sort.Order.desc("age"));
|
||||
|
||||
// order by absent
|
||||
assertThat(createQueryFor("select u from user u", sort)).isEqualTo("select u from user u order by u.age desc");
|
||||
|
||||
// order by present
|
||||
assertThat(createQueryFor("select u from user u order by u.lastname", sort))
|
||||
.isEqualTo("select u from user u order by u.lastname, u.age desc");
|
||||
}
|
||||
|
||||
@Test // GH-2511
|
||||
void countQueryUsesCorrectVariable() {
|
||||
|
||||
assertThat(createCountQueryFor("SELECT e FROM User e WHERE created_at > $1"))
|
||||
.isEqualTo("SELECT count(e) FROM User e WHERE created_at > $1");
|
||||
|
||||
assertThat(
|
||||
createCountQueryFor("SELECT t FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"))
|
||||
.isEqualTo("SELECT count(t) FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'");
|
||||
|
||||
assertThat(createCountQueryFor("select s FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)"))
|
||||
.isEqualTo("select count(s) FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)");
|
||||
|
||||
assertThat(
|
||||
createCountQueryFor("SELECT us FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"))
|
||||
.isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)");
|
||||
}
|
||||
|
||||
@Test // GH-2496, GH-2522, GH-2537, GH-2045
|
||||
void orderByShouldWorkWithSubSelectStatements() {
|
||||
|
||||
Sort sort = Sort.by(Sort.Order.desc("age"));
|
||||
|
||||
assertThat(createQueryFor("""
|
||||
select r
|
||||
From DataRecord r
|
||||
where
|
||||
(
|
||||
r.adusrId = :userId
|
||||
or EXISTS( select 1 FROM DataRecordDvsRight dr WHERE dr.adusrId = :userId AND dr.dataRecord = r )
|
||||
)""", sort)).endsWith("order by r.age desc");
|
||||
|
||||
assertThat(createQueryFor("""
|
||||
select distinct u
|
||||
from FooBar u
|
||||
where u.role = 'redacted'
|
||||
and (
|
||||
not exists (
|
||||
select g from FooBarGroup g
|
||||
where g in :excludedGroups
|
||||
)
|
||||
)""", sort)).endsWith("order by u.age desc");
|
||||
|
||||
assertThat(createQueryFor("""
|
||||
SELECT i
|
||||
FROM Item i
|
||||
WHERE i.id IN (
|
||||
SELECT max(i2.id) FROM Item i2
|
||||
WHERE i2.field.id = :fieldId
|
||||
GROUP BY i2.field.id, i2.version)""", sort)).endsWith("order by i.age desc");
|
||||
}
|
||||
|
||||
@Test // GH-2074
|
||||
void queryParserPicksCorrectAliasAmidstMultipleAliases() {
|
||||
assertThat(alias("select u from User as u left join u.roles as r")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("queriesWithReservedWordsAsIdentifiers") // GH-2864
|
||||
void usingReservedWordAsRelationshipNameShouldWork(String relationshipName, String joinAlias) {
|
||||
|
||||
EqlQueryParser.parseQuery(String.format("""
|
||||
select u
|
||||
from UserAccountEntity u
|
||||
join u.lossInspectorLimitConfiguration lil
|
||||
join u.companyTeam ct
|
||||
where exists (
|
||||
select iu
|
||||
from UserAccountEntity iu
|
||||
join iu.roles u2r
|
||||
join u2r.role r
|
||||
join r.rights r2r
|
||||
join r2r.%s %s
|
||||
where
|
||||
%s.code = :rightCode
|
||||
and iu = u
|
||||
)
|
||||
and ct.id = :teamId
|
||||
""", relationshipName, joinAlias, joinAlias));
|
||||
}
|
||||
|
||||
@Test // GH-664, GH-1066, GH-2960
|
||||
void sortingRecognizesJoinAliases() {
|
||||
|
||||
String query = "select p from Customer c join c.productOrder p where p.delayed = true";
|
||||
|
||||
assertThat(createQueryFor(query, Sort.by(Sort.Order.desc("lastName")))).isEqualToIgnoringWhitespace("""
|
||||
select p from Customer c
|
||||
join c.productOrder p
|
||||
where p.delayed = true
|
||||
order by c.lastName desc
|
||||
""");
|
||||
|
||||
assertThat(createQueryFor(query, Sort.by(Sort.Order.desc("p.lineItems")))).isEqualToIgnoringWhitespace("""
|
||||
select p from Customer c
|
||||
join c.productOrder p
|
||||
where p.delayed = true
|
||||
order by p.lineItems desc
|
||||
""");
|
||||
}
|
||||
|
||||
static Stream<Arguments> queriesWithReservedWordsAsIdentifiers() {
|
||||
|
||||
return Stream.of( //
|
||||
Arguments.of("right", "rt"), //
|
||||
Arguments.of("left", "lt"), //
|
||||
Arguments.of("outer", "ou"), //
|
||||
Arguments.of("full", "full"), //
|
||||
Arguments.of("inner", "inr"));
|
||||
}
|
||||
|
||||
private void assertCountQuery(String originalQuery, String countQuery) {
|
||||
assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery);
|
||||
}
|
||||
|
||||
private String createQueryFor(String query, Sort sort) {
|
||||
return newParser(query).applySorting(sort);
|
||||
}
|
||||
|
||||
private String createCountQueryFor(String query) {
|
||||
return createCountQueryFor(query, null);
|
||||
}
|
||||
|
||||
private String createCountQueryFor(String original, @Nullable String countProjection) {
|
||||
return newParser(original).createCountQueryFor(countProjection);
|
||||
}
|
||||
|
||||
private String alias(String query) {
|
||||
return newParser(query).detectAlias();
|
||||
}
|
||||
|
||||
private boolean hasConstructorExpression(String query) {
|
||||
return newParser(query).hasConstructorExpression();
|
||||
}
|
||||
|
||||
private String projection(String query) {
|
||||
return newParser(query).getProjection();
|
||||
}
|
||||
|
||||
private QueryEnhancer newParser(String query) {
|
||||
return JpaQueryEnhancer.forEql(DeclaredQuery.of(query, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,887 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests built around examples of EQL found in the JPA spec
|
||||
* https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc<br/>
|
||||
* <br/>
|
||||
* IMPORTANT: Purely verifies the parser without any transformations.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class EqlSpecificationTests {
|
||||
|
||||
private static final String SPEC_FAULT = "Disabled due to spec fault> ";
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example
|
||||
*/
|
||||
@Test
|
||||
void joinExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order AS o JOIN o.lineItems AS l
|
||||
WHERE l.shipped = FALSE
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#identification-variables
|
||||
*/
|
||||
@Test
|
||||
void joinExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l JOIN l.product p
|
||||
WHERE p.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#range-variable-declarations
|
||||
*/
|
||||
@Test
|
||||
void rangeVariableDeclarations() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o1
|
||||
FROM Order o1, Order o2
|
||||
WHERE o1.quantity > o2.quantity AND
|
||||
o2.customer.lastname = 'Smith' AND
|
||||
o2.customer.firstname= 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT i.name, VALUE(p)
|
||||
FROM Item i JOIN i.photos p
|
||||
WHERE KEY(p) LIKE '%egret'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT i.name, p
|
||||
FROM Item i JOIN i.photos p
|
||||
WHERE KEY(p) LIKE '%egret'
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample3() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo.phones p
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#path-expressions
|
||||
*/
|
||||
@Test
|
||||
void pathExpressionsExample4() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
|
||||
WHERE e.contactInfo.address.zipcode = '95054'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathExpressionSyntaxExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT l.product
|
||||
FROM Order AS o JOIN o.lineItems l
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c FROM Customer c, Employee e WHERE c.hatsize = e.shoesize
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsInnerExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinsInExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doubleJoinExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p.vendor
|
||||
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
|
||||
WHERE c.address.zipcode = '95054'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinOnExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
ON p.status = 'inStock'
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinWhereExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT s.name, COUNT(p)
|
||||
FROM Suppliers s LEFT JOIN s.products p
|
||||
WHERE p.status = 'inStock'
|
||||
GROUP BY s.name
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftJoinFetchExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT d
|
||||
FROM Department d LEFT JOIN FETCH d.employees
|
||||
WHERE d.deptno = 1
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectionMemberExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.product.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectionMemberInExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o, IN(o.lineItems) l
|
||||
WHERE l.product.productType = 'office_supplies'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order AS o JOIN o.lineItems l JOIN l.product p
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT b.name, b.ISBN
|
||||
FROM Order o JOIN TREAT(o.product AS Book) b
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp
|
||||
WHERE lp.budget > 1000
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #fromClauseDowncastingExample3fixed()
|
||||
*/
|
||||
@Test
|
||||
@Disabled(SPEC_FAULT + "Use double-quotes when it should be using single-quotes for a string literal")
|
||||
void fromClauseDowncastingExample3_SPEC_BUG() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e FROM Employee e JOIN e.projects p
|
||||
WHERE TREAT(p AS LargeProject).budget > 1000
|
||||
OR TREAT(p AS SmallProject).name LIKE 'Persist%'
|
||||
OR p.description LIKE "cost overrun"
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample3fixed() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e FROM Employee e JOIN e.projects p
|
||||
WHERE TREAT(p AS LargeProject).budget > 1000
|
||||
OR TREAT(p AS SmallProject).name LIKE 'Persist%'
|
||||
OR p.description LIKE 'cost overrun'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromClauseDowncastingExample4() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e FROM Employee e
|
||||
WHERE TREAT(e AS Exempt).vacationDays > 10
|
||||
OR TREAT(e AS Contractor).hours > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathExpressionsNamedParametersExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE c.status = :stat
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void betweenExpressionsExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT t
|
||||
FROM CreditCard c JOIN c.transactionHistory t
|
||||
WHERE c.holder.name = 'John Doe' AND INDEX(t) BETWEEN 0 AND 9
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void memberOfExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p
|
||||
FROM Person p
|
||||
WHERE 'Joe' MEMBER OF p.nicknames
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsSubSelectExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT emp
|
||||
FROM Employee emp
|
||||
WHERE EXISTS (
|
||||
SELECT spouseEmp
|
||||
FROM Employee spouseEmp
|
||||
WHERE spouseEmp = emp.spouse)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT emp
|
||||
FROM Employee emp
|
||||
WHERE emp.salary > ALL (
|
||||
SELECT m.salary
|
||||
FROM Manager m
|
||||
WHERE m.department = emp.department)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsSubSelectExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT emp
|
||||
FROM Employee emp
|
||||
WHERE EXISTS (
|
||||
SELECT spouseEmp
|
||||
FROM Employee spouseEmp
|
||||
WHERE spouseEmp = emp.spouse)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subselectNumericComparisonExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE (SELECT AVG(o.price) FROM c.orders o) > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subselectNumericComparisonExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT goodCustomer
|
||||
FROM Customer goodCustomer
|
||||
WHERE goodCustomer.balanceOwed < (
|
||||
SELECT AVG(c.balanceOwed)/2.0 FROM Customer c)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexExample() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT w.name
|
||||
FROM Course c JOIN c.studentWaitlist w
|
||||
WHERE c.name = 'Calculus'
|
||||
AND INDEX(w) = 0
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #functionInvocationExampleWithCorrection()
|
||||
*/
|
||||
@Test
|
||||
@Disabled(SPEC_FAULT + "FUNCTION calls needs a comparator")
|
||||
void functionInvocationExample_SPEC_BUG() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionInvocationExampleWithCorrection() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c
|
||||
FROM Customer c
|
||||
WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateCaseExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.salary =
|
||||
CASE WHEN e.rating = 1 THEN e.salary * 1.1
|
||||
WHEN e.rating = 2 THEN e.salary * 1.05
|
||||
ELSE e.salary * 1.01
|
||||
END
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateCaseExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.salary =
|
||||
CASE e.rating WHEN 1 THEN e.salary * 1.1
|
||||
WHEN 2 THEN e.salary * 1.05
|
||||
ELSE e.salary * 1.01
|
||||
END
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectCaseExample1() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e.name,
|
||||
CASE TYPE(e) WHEN Exempt THEN 'Exempt'
|
||||
WHEN Contractor THEN 'Contractor'
|
||||
WHEN Intern THEN 'Intern'
|
||||
ELSE 'NonExempt'
|
||||
END
|
||||
FROM Employee e
|
||||
WHERE e.dept.name = 'Engineering'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectCaseExample2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e.name,
|
||||
f.name,
|
||||
CONCAT(CASE WHEN f.annualMiles > 50000 THEN 'Platinum '
|
||||
WHEN f.annualMiles > 25000 THEN 'Gold '
|
||||
ELSE ''
|
||||
END,
|
||||
'Frequent Flyer')
|
||||
FROM Employee e JOIN e.frequentFlierPlan f
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN (Exempt, Contractor)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest2() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN (:empType1, :empType2)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest3() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) IN :empTypes
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest4() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT TYPE(e)
|
||||
FROM Employee e
|
||||
WHERE TYPE(e) <> Exempt
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest5() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c.status, AVG(c.filledOrderCount), COUNT(c)
|
||||
FROM Customer c
|
||||
GROUP BY c.status
|
||||
HAVING c.status IN (1, 2)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest6() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c.country, COUNT(c)
|
||||
FROM Customer c
|
||||
GROUP BY c.country
|
||||
HAVING COUNT(c) > 30
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest7() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c, COUNT(o)
|
||||
FROM Customer c JOIN c.orders o
|
||||
GROUP BY c
|
||||
HAVING COUNT(o) >= 5
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest8() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c.id, c.status
|
||||
FROM Customer c JOIN c.orders o
|
||||
WHERE o.count > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest9() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT v.location.street, KEY(i).title, VALUE(i)
|
||||
FROM VideoStore v JOIN v.videoInventory i
|
||||
WHERE v.location.zipcode = '94301' AND VALUE(i) > 0
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest10() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o.lineItems FROM Order AS o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest11() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT c, COUNT(l) AS itemCount
|
||||
FROM Customer c JOIN c.Orders o JOIN o.lineItems l
|
||||
WHERE c.address.state = 'CA'
|
||||
GROUP BY c
|
||||
ORDER BY itemCount
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest12() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count)
|
||||
FROM Customer c JOIN c.orders o
|
||||
WHERE o.count > 100
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest13() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT e.address AS addr
|
||||
FROM Employee e
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest14() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT AVG(o.quantity) FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest15() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT SUM(l.price)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest16() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT COUNT(o) FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest17() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT COUNT(l.price)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest18() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT COUNT(l)
|
||||
FROM Order o JOIN o.lineItems l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John' AND l.price IS NOT NULL
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest19() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
ORDER BY o.quantity DESC, o.totalcost
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest20() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o.quantity, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
ORDER BY o.quantity, a.zipcode
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest21() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA' AND a.county = 'Santa Clara'
|
||||
ORDER BY o.quantity, taxedCost, a.zipcode
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest22() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT AVG(o.quantity) as q, a.zipcode
|
||||
FROM Customer c JOIN c.orders o JOIN c.address a
|
||||
WHERE a.state = 'CA'
|
||||
GROUP BY a.zipcode
|
||||
ORDER BY q DESC
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest23() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p.product_name
|
||||
FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
ORDER BY p.price
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* This query is specifically dubbed illegal in the spec. It may actually be failing for a different reason.
|
||||
*/
|
||||
@Test
|
||||
void theRest24() {
|
||||
|
||||
assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> {
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT p.product_name
|
||||
FROM Order o, IN(o.lineItems) l JOIN o.customer c
|
||||
WHERE c.lastname = 'Smith' AND c.firstname = 'John'
|
||||
ORDER BY o.quantity
|
||||
""");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest25() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
DELETE
|
||||
FROM Customer c
|
||||
WHERE c.status = 'inactive'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest26() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
DELETE
|
||||
FROM Customer c
|
||||
WHERE c.status = 'inactive'
|
||||
AND c.orders IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest27() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
UPDATE Customer c
|
||||
SET c.status = 'outstanding'
|
||||
WHERE c.balance < 10000
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest28() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
UPDATE Employee e
|
||||
SET e.address.building = 22
|
||||
WHERE e.address.building = 14
|
||||
AND e.address.city = 'Santa Clara'
|
||||
AND e.project = 'Jakarta EE'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest29() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest30() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.shippingAddress.state = 'CA'
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest31() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o.shippingAddress.state
|
||||
FROM Order o
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest32() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest33() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS NOT EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest34() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.lineItems IS EMPTY
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest35() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.shipped = FALSE
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest36() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE
|
||||
NOT (o.shippingAddress.state = o.billingAddress.state AND
|
||||
o.shippingAddress.city = o.billingAddress.city AND
|
||||
o.shippingAddress.street = o.billingAddress.street)
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest37() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT o
|
||||
FROM Order o
|
||||
WHERE o.shippingAddress <> o.billingAddress
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest38() {
|
||||
|
||||
EqlQueryParser.parseQuery("""
|
||||
SELECT DISTINCT o
|
||||
FROM Order o JOIN o.lineItems l
|
||||
WHERE l.product.name = ?1
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -523,7 +523,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest() {
|
||||
void inClauseWithTypeLiteralsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
@@ -533,7 +533,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest2() {
|
||||
void inClauseWithParametersShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
@@ -543,7 +543,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest3() {
|
||||
void inClauseWithSingleParameterShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e
|
||||
@@ -553,7 +553,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest4() {
|
||||
void notEqualsForTypeShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT TYPE(e)
|
||||
@@ -563,7 +563,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest5() {
|
||||
void havingWithInClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.status, AVG(c.filledOrderCount), COUNT(c)
|
||||
@@ -574,7 +574,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest6() {
|
||||
void havingClauseWithComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.country, COUNT(c)
|
||||
@@ -585,7 +585,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest7() {
|
||||
void havingClauseWithAnotherComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c, COUNT(o)
|
||||
@@ -596,7 +596,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest8() {
|
||||
void whereClauseWithComparisonShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c.id, c.status
|
||||
@@ -606,7 +606,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest9() {
|
||||
void keyValueFunctionsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT v.location.street, KEY(i).title, VALUE(i)
|
||||
@@ -616,7 +616,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest10() {
|
||||
void fromClauseWithAsShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.lineItems FROM Order AS o
|
||||
@@ -624,7 +624,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest11() {
|
||||
void countFunctionWithAsClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT c, COUNT(l) AS itemCount
|
||||
@@ -636,7 +636,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest12() {
|
||||
void objectConstructionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count)
|
||||
@@ -646,7 +646,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest13() {
|
||||
void selectWithAsClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT e.address AS addr
|
||||
@@ -655,7 +655,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest14() {
|
||||
void averageFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT AVG(o.quantity) FROM Order o
|
||||
@@ -663,7 +663,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest15() {
|
||||
void sumFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT SUM(l.price)
|
||||
@@ -673,7 +673,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest16() {
|
||||
void countFunctionShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(o) FROM Order o
|
||||
@@ -681,7 +681,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest17() {
|
||||
void countFunctionOnSubElementShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(l.price)
|
||||
@@ -691,7 +691,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest18() {
|
||||
void equivalentCountFunctionShouldAlsoWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT COUNT(l)
|
||||
@@ -701,7 +701,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest19() {
|
||||
void orderByBasedOnSelectClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -712,7 +712,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest20() {
|
||||
void orderByThatMatchesSelectClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.quantity, a.zipcode
|
||||
@@ -723,7 +723,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest21() {
|
||||
void orderByThatMatchesAllSelectAliasesShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode
|
||||
@@ -734,7 +734,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest22() {
|
||||
void orderByThatMatchesSelectFunctionAliasShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT AVG(o.quantity) as q, a.zipcode
|
||||
@@ -745,8 +745,13 @@ class JpqlQueryRendererTests {
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This query is specifically dubbed illegal in the spec. However, it's not due to a grammar failure but instead
|
||||
* for semantic reasons. Our parser does NOT check if the ORDER BY matches the SELECT or not. Hence, this is left to
|
||||
* the JPA provider.
|
||||
*/
|
||||
@Test
|
||||
void theRest23() {
|
||||
void orderByClauseThatIsNotReflectedInTheSelectClause() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT p.product_name
|
||||
@@ -757,10 +762,10 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* This query is specifically dubbed illegal in the spec. It may actually be failing for a different reason.
|
||||
* NOTE: This query is specifically dubbed illegal in the spec. It may actually be failing for a different reason.
|
||||
*/
|
||||
@Test
|
||||
void theRest24() {
|
||||
void orderByClauseThatIsNotReflectedInTheSelectClauseButAlsoHasAnInClauseInTheFromClause() {
|
||||
|
||||
assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> {
|
||||
assertQuery("""
|
||||
@@ -773,7 +778,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest25() {
|
||||
void simpleDeleteShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
DELETE
|
||||
@@ -783,7 +788,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest26() {
|
||||
void deleteWithMoreComplexCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
DELETE
|
||||
@@ -794,7 +799,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest27() {
|
||||
void simpleUpdateShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Customer c
|
||||
@@ -804,7 +809,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest28() {
|
||||
void moreComplexUpdateShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
UPDATE Employee e
|
||||
@@ -816,7 +821,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest29() {
|
||||
void simpleSelectShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -825,7 +830,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest30() {
|
||||
void selectWithWhereClauseShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -835,7 +840,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest31() {
|
||||
void selectWithDistinctSubElementShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o.shippingAddress.state
|
||||
@@ -844,7 +849,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest32() {
|
||||
void selectWithSimpleDistinctShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
@@ -853,7 +858,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest33() {
|
||||
void selectWithIsNotEmptyCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -863,7 +868,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest34() {
|
||||
void selectWithIsEmptyCriteriaShouldWork() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -873,7 +878,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest35() {
|
||||
void findAllPendingOrders() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
@@ -883,7 +888,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest36() {
|
||||
void findAllOrdersWhereShippingAddressDoesNotMatchBillingAddress() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -896,7 +901,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest37() {
|
||||
void simplerVersionOfShippingAddressNotMatchingBillingAddress() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT o
|
||||
@@ -906,7 +911,7 @@ class JpqlQueryRendererTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void theRest38() {
|
||||
void findOrdersThatHaveProductNamedByAParameter() {
|
||||
|
||||
assertQuery("""
|
||||
SELECT DISTINCT o
|
||||
|
||||
Reference in New Issue
Block a user