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
| StandaloneExpression
| StandaloneCondExpr
| StandaloneOrderBy;
SourcelessSelect = select {GroupBy}? SelectEnd;
SelectEnd = Model
| AnyYield;
Where = where Condition;
Condition = Predicate
| AndCondition
| OrCondition
| CompoundCondition
| NegatedCompoundCondition;
Predicate = UnaryPredicate
| ComparisonPredicate
| QuantifiedComparisonPredicate
| LikePredicate
| MembershipPredicate
| SingleConditionPredicate;
UnaryComparisonOperator = isNull
| isNotNull;
LikeOperator = like
| iLike
| likeWithCast
| iLikeWithCast
| notLike
| notLikeWithCast
| notILikeWithCast
| notILike;
ComparisonOperand = SingleOperand
| MultiOperand;
ComparisonOperator = eq
| gt
| lt
| ge
| le
| ne;
QuantifiedOperand = all(<SingleResultQueryModel>)
| any(<SingleResultQueryModel>);
ExprBody = SingleOperand {ArithmeticalOperator SingleOperand}*;
ArithmeticalOperator = add
| sub
| div
| mult
| mod;
SingleOperand = Prop
| ExtProp
| Val
| Param
| expr(<ExpressionModel>)
| model(<SingleResultQueryModel>)
| UnaryFunction
| IfNull
| now
| DateDiffInterval
| DateAddInterval
| Round
| Concat
| CaseWhen
| Expr;
UnaryFunctionName = upperCase
| lowerCase
| secondOf
| minuteOf
| hourOf
| dayOf
| monthOf
| yearOf
| dayOfWeekOf
| absOf
| dateOf;
DateIntervalUnit = seconds
| minutes
| hours
| days
| months
| years;
CaseWhenEnd = end
| endAsInt
| endAsBool
| endAsStr(<Integer>)
| endAsDecimal(<Integer>, <Integer>);
MultiOperand = anyOfProps(<CharSequence>*)
| allOfProps(<CharSequence>*)
| anyOfValues(<Object>*)
| allOfValues(<Object>*)
| anyOfParams(<CharSequence>*)
| anyOfIParams(<CharSequence>*)
| allOfParams(<CharSequence>*)
| allOfIParams(<CharSequence>*)
| anyOfModels(<PrimitiveResultQueryModel>*)
| allOfModels(<PrimitiveResultQueryModel>*)
| anyOfExpressions(<ExpressionModel>*)
| allOfExpressions(<ExpressionModel>*);
MembershipOperator = in
| notIn;
MembershipOperand = values(<Object>*)
| props(<CharSequence>*)
| params(<CharSequence>*)
| iParams(<CharSequence>*)
| model(<SingleResultQueryModel>);
Join = JoinOperator {as(<CharSequence>)}? JoinCondition {Join}?;
JoinOperator = join(<Class>)
| join(<EntityResultQueryModel>)
| join(<AggregatedResultQueryModel>)
| leftJoin(<Class>)
| leftJoin(<EntityResultQueryModel>)
| leftJoin(<AggregatedResultQueryModel>);
JoinCondition = on Condition;
GroupBy = {groupBy SingleOperand}+;
AnyYield = YieldAll
| YieldSome;
YieldTail = Yield1Tail
| YieldManyTail;
AliasedYield = yield YieldOperand YieldAlias;
YieldOperand = SingleOperand
| YieldOperandExpr
| countAll
| YieldOperandFunction;
YieldOperandFunctionName = maxOf
| minOf
| sumOf
| countOf
| avgOf
| sumOfDistinct
| countOfDistinct
| avgOfDistinct;
YieldAlias = as(<CharSequence>)
| as(<Enum>)
| asRequired(<CharSequence>)
| asRequired(<Enum>);
Yield1Model = modelAsEntity(<Class>)
| modelAsPrimitive;
YieldManyModel = modelAsEntity(<Class>)
| modelAsAggregate;
Model = model
| modelAsEntity(<Class>)
| modelAsAggregate;
StandaloneCondition = Predicate
| AndStandaloneCondition
| OrStandaloneCondition;
OrderBy = orderBy {OrderByOperand}+ {Limit}? {Offset}?;
OrderByOperand = OrderByOperand_Single
| OrderByOperand_Yield
| OrderByOperand_OrderingModel;
Order = asc
| desc;
Select = SelectFrom
| SourcelessSelect;
SelectFrom = SelectSource {as(<CharSequence>)}? {Join}? {Where}? {GroupBy}? {OrderBy}? SelectEnd;
SelectSource = select(<Class>)
| select(<EntityResultQueryModel>*)
| select(<AggregatedResultQueryModel>*);
StandaloneExpression = expr YieldOperand {ArithmeticalOperator YieldOperand}* model;
StandaloneCondExpr = cond StandaloneCondition model;
StandaloneOrderBy = orderBy {OrderByOperand}+ {Limit}? {Offset}? model;
AndCondition = Condition and Condition;
OrCondition = Condition or Condition;
CompoundCondition = begin Condition end;
NegatedCompoundCondition = notBegin Condition end;
UnaryPredicate = ComparisonOperand UnaryComparisonOperator;
ComparisonPredicate = ComparisonOperand ComparisonOperator ComparisonOperand;
QuantifiedComparisonPredicate = ComparisonOperand ComparisonOperator QuantifiedOperand;
LikePredicate = ComparisonOperand LikeOperator ComparisonOperand;
MembershipPredicate = ComparisonOperand MembershipOperator MembershipOperand;
SingleConditionPredicate = exists(<QueryModel>)
| notExists(<QueryModel>)
| existsAnyOf(<QueryModel>*)
| notExistsAnyOf(<QueryModel>*)
| existsAllOf(<QueryModel>*)
| notExistsAllOf(<QueryModel>*)
| critCondition(<CharSequence>, <CharSequence>)
| critCondition(<ICompoundCondition0>, <CharSequence>, <CharSequence>)
| critCondition(<ICompoundCondition0>, <CharSequence>, <CharSequence>, <Object>)
| condition(<ConditionModel>)
| negatedCondition(<ConditionModel>);
Prop = prop(<CharSequence>)
| prop(<Enum>);
ExtProp = extProp(<CharSequence>)
| extProp(<Enum>);
Val = val(<Object>)
| iVal(<Object>);
Param = param(<CharSequence>)
| param(<Enum>)
| iParam(<CharSequence>)
| iParam(<Enum>);
UnaryFunction = UnaryFunctionName SingleOperand;
IfNull = ifNull SingleOperand then SingleOperand;
DateDiffInterval = count DateIntervalUnit between SingleOperand and SingleOperand;
DateAddInterval = addTimeIntervalOf SingleOperand DateIntervalUnit to SingleOperand;
Round = round SingleOperand to(<Integer>);
Concat = concat SingleOperand {with SingleOperand}* end;
CaseWhen = caseWhen Condition then SingleOperand {when Condition then SingleOperand}* {otherwise SingleOperand}? CaseWhenEnd;
Expr = beginExpr ExprBody endExpr;
YieldAll = yieldAll {AliasedYield}* YieldManyModel;
YieldSome = yield YieldOperand YieldTail;
Yield1Tail = Yield1Model;
YieldManyTail = YieldAlias {AliasedYield}* YieldManyModel;
YieldOperandFunction = YieldOperandFunctionName SingleOperand;
YieldOperandExpr = beginExpr YieldOperand {ArithmeticalOperator YieldOperand}* endExpr;
AndStandaloneCondition = StandaloneCondition and StandaloneCondition;
OrStandaloneCondition = StandaloneCondition or StandaloneCondition;
OrderByOperand_Single = SingleOperand Order;
OrderByOperand_Yield = yield(<CharSequence>) Order;
OrderByOperand_OrderingModel = order(<OrderingModel>);
Limit = limit(<long>)
| limit(<Limit>);
Offset = offset(<long>);
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)