EQL - fieldenms/tg GitHub Wiki
EQL -- Entity Query Language.
EQL is a Java DSL that compiles to SQL. EQL is an expression-based language -- everything is an expression. EQL expressions are written using a Fluent API.
An EQL expression is represented by a query model, which is a sequence of tokens that represents a method call chain in the Fluent API. A query model is the equivalent of the output of a tokenizer for a standalone programming language (e.g., SQL).
Compilation to SQL is performed in stages. The input of the initial stage is a query model and the result of the last stage is:
- an SQL statement;
- information required to transform the raw results of executing the SQL into meaningful results (entity instances).
A variant of the Backus-Naur Form (BNF) is employed with the following extensions:
-
{ s1, ..., sn }
-- grouping of symbols -
{ ... }?
-- optional (none or one) -
{ ... }*
-- repetition (none or more) -
{ ... }+
-- one or more -
s(<Type1>, ..., <Typen>)
-- parameterisation of a symbol; the corresponding Java method will declaren
parameters with respective types -
s(..., <Typen>*)
-- parameterisation of a symbol with variable arity in the last parameter
Terminals start with a lowercase letter, non-terminals -- with an uppercase letter.
Query = Select
| Expression;
Select = select(<Class>) { as(<String>) }? { Join }? { Where }? { GroupBy }? FirstYield
| select(<Class>) { as(<String>) }? { Join }? { Where }? { GroupBy }? Model;
Where = where Condition;
Condition = Predicate
| Condition and Condition
| Condition or Condition
| begin Condition end;
Predicate = Operand UnaryComparisonOperator
| Operand ComparisonOperator ComparisonOperand
| Operand QuantifiedComparisonOperator QuantifiedOperand
| Operand MembershipOperator MembershipOperand
| SingleConditionPredicate;
UnaryComparisonOperator = isNull
| isNotNull;
ComparisonOperator = like
| iLike
| likeWithCast
| iLikeWithCast
| notLike
| notLikeWithCast
| notILikeWithCast
| notILike;
ComparisonOperand = SingleOperand
| Expr
| MultiOperand;
QuantifiedComparisonOperator = eq
| gt
| lt
| ge
| le
| ne;
QuantifiedOperand = all(<SingleResultQueryModel>)
| any(<SingleResultQueryModel>)
| ComparisonOperand;
Expr = beginExpr ExprBody endExpr;
ExprBody = SingleOperandOrExpr { ArithmeticalOperator SingleOperandOrExpr }*;
SingleOperandOrExpr = SingleOperand
| Expr;
ArithmeticalOperator = add
| sub
| div
| mult
| mod;
SingleOperand = AnyProp
| Val
| Param
| expr(<ExpressionModel>)
| model(<SingleResultQueryModel>)
| UnaryFunction
| IfNull
| now
| DateDiffInterval
| DateAddInterval
| Round
| Concat
| CaseWhen;
UnaryFunction = UnaryFunctionName SingleOperandOrExpr;
UnaryFunctionName = upperCase
| lowerCase
| secondOf
| minuteOf
| hourOf
| dayOf
| monthOf
| yearOf
| dayOfWeekOf
| absOf
| dateOf;
IfNull = ifNull SingleOperandOrExpr then SingleOperandOrExpr;
DateDiffInterval = count DateDiffIntervalUnit between SingleOperandOrExpr and SingleOperandOrExpr;
DateDiffIntervalUnit = seconds
| minutes
| hours
| days
| months
| years;
DateAddInterval = addTimeIntervalOf SingleOperandOrExpr DateAddIntervalUnit to SingleOperandOrExpr;
DateAddIntervalUnit = seconds
| minutes
| hours
| days
| months
| years;
Round = round SingleOperandOrExpr to(<Integer>);
Concat = concat SingleOperandOrExpr { with SingleOperandOrExpr }* end;
CaseWhen = caseWhen Condition then SingleOperandOrExpr { when Condition then SingleOperandOrExpr }* { otherwise SingleOperandOrExpr }? CaseWhenEnd;
CaseWhenEnd = end
| endAsInt
| endAsBool
| endAsStr(<Integer>)
| endAsDecimal(<Integer>, <Integer>);
AnyProp = Prop
| ExtProp;
Prop = prop(<String>)
| prop(<IConvertableToPath>)
| prop(<Enum>);
ExtProp = extProp(<String>)
| extProp(<IConvertableToPath>)
| extProp(<Enum>);
Val = val(<Object>)
| iVal(<Object>);
Param = param(<String>)
| param(<Enum>)
| iParam(<String>)
| iParam(<Enum>);
MultiOperand = anyOfProps(<String>*)
| anyOfProps(<IConvertableToPath>*)
| allOfProps(<String>*)
| allOfProps(<IConvertableToPath>*)
| anyOfValues(<Object>*)
| allOfValues(<Object>*)
| anyOfParams(<String>*)
| anyOfIParams(<String>*)
| allOfParams(<String>*)
| allOfIParams(<String>*)
| anyOfModels(<PrimitiveResultQueryModel>*)
| allOfModels(<PrimitiveResultQueryModel>*)
| anyOfExpressions(<ExpressionModel>*)
| allOfExpressions(<ExpressionModel>*);
MembershipOperator = in
| notIn;
MembershipOperand = values(<Object>*)
| props(<String>*)
| props(<IConvertableToPath>*)
| params(<String>*)
| iParams(<String>*)
| model(<SingleResultQueryModel>);
SingleConditionPredicate = exists(<QueryModel>)
| notExists(<QueryModel>)
| existsAnyOf(<QueryModel>*)
| notExistsAnyOf(<QueryModel>*)
| existsAllOf(<QueryModel>*)
| notExistsAllOf(<QueryModel>*)
| critCondition(<String>, <String>)
| critCondition(<IConvertableToPath>, <IConvertableToPath>)
| critCondition(<ICompoundCondition0>, <String>, <String>)
| critCondition(<ICompoundCondition0>, <String>, <String>, <Object>)
| condition(<ConditionModel>)
| negatedCondition(<ConditionModel>);
Join = JoinOperator { as(<String>) }? JoinCondition { Join }?;
JoinOperator = join(<Class>)
| join(<EntityResultQueryModel>)
| join(<AggregatedResultQueryModel>)
| leftJoin(<Class>)
| leftJoin(<EntityResultQueryModel>)
| leftJoin(<AggregatedResultQueryModel>);
JoinCondition = on Condition;
GroupBy = groupBy SingleOperandOrExpr { GroupBy }?;
FirstYield = yield YieldOperand modelAsEntity(<Class>)
| yield YieldOperand modelAsPrimitive
| yieldAll SubsequentYield
| yield YieldOperand YieldAlias SubsequentYield;
YieldOperand = SingleOperandOrExpr
| countAll
| YieldOperandFunction;
YieldOperandFunction = YieldOperandFunctionName SingleOperandOrExpr;
YieldOperandFunctionName = maxOf
| minOf
| sumOf
| countOf
| avgOf
| sumOfDistinct
| countOfDistinct
| avgOfDistinct;
YieldAlias = as(<String>)
| as(<Enum>)
| as(<IConvertableToPath>)
| asRequired(<String>)
| asRequired(<Enum>)
| asRequired(<IConvertableToPath>);
SubsequentYield = yield YieldOperand YieldAlias SubsequentYield
| modelAsEntity(<Class>)
| modelAsAggregate;
Model = model
| modelAsEntity(<Class>)
| modelAsAggregate;
Expression = expr model;
If all clauses return val(null)
, then one of endAs*
constructs must be used.
It is illegal to use end()
in such an expression, otherwise EQL would have to guess which data type the user intended.
Example:
// illegal!
caseWhen().prop("amount").eq().val(54).then().val(null).otherwise().val(null).end();
// illegal!
caseWhen().prop("amount").eq().val(54).then().val(null).end();
// good
caseWhen().prop("amount").eq().val(54).then().val(null).endAsInt();
-
Subchaining -- a style of fluent API design that passes a chain to a method call in another chain as its argument.
select(X, where( prop("x").gt(5) ))
-
Non-subchaining -- style of fluent API design that is the opposite subchaining -- all methods must be called sequentially.
select(X).where().prop("x").gt(5)