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).

Grammar

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 declare n 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;

Reference

caseWhen

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();

Glossary

  • 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)

⚠️ **GitHub.com Fallback** ⚠️