Skip to content
homedirectory edited this page Apr 10, 2024 · 11 revisions

EQL - Entity Query Language.

Think of EQL as an internal DSL[1], i.e., a language inside another, host language. An internal language is written using the constructs of its host language, rather than text. Consequently, the act of writing "code" in an internal language is equivalent to building an AST for that code. Therefore, there is no need for parsing, unlike in standalone languages which are written in text.

EQL is an expression-based language -- everything is an expression.

EQL's host language is Java. EQL expressions are written using a fluent interface defined in Java.

An EQL expression is represented by a query model, which is just a sequence of tokens that correspond to method calls on a fluent interface. A query model is the equivalent of the output of a tokenizer in a text-based language.

Transformation happens in stages where the input is a query model and the result is composed of:

  • an SQL expression corresponding to the input EQL expression;
  • information required to transform the raw results of executing the SQL into meaningful results (entity instances).

[1] Martin Fowler: "Internal DSLs are particular ways of using a host language to give the host language the feel of a particular language".

Grammar

A variant of Backus-Naur Form 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.

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 of terms

  • Subchaining -- a style of fluent API design that passes a chain to a method call in another chain as its argument. E.g., 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. E.g., select(X).where().prop("x").gt(5)
Clone this wiki locally