EQL Guide - fieldenms/tg GitHub Wiki

Table of contents

Resolution of component-typed properties

When a component-typed property is used as an operand (e.g., comparison condition, yield operand), it has to be resolved against the source of the query. Since a property is a single-valued (scalar) operand, and a component type may have multiple components, a component-typed property implicitly represents one of its components.

As of TG version 2.0.0, the following implicit representations are used:

  • Money - sub-property amount.
  • RichText - sub-property searchText.

For example, the following yields can be used interchangeably:

yield().prop("price") // implicitly resolved to "price.amount"
yield().prop("price.amount")

Yielding component-typed properties

This section describes how to use component-typed properties in yields, both as yield operands and aliases.

The treatment of component-typed properties as yield operands is the same as in other contexts. Refer to the section about resolution of component-typed properties.

TG version 2.0.0

Support for component-typed properties in yield aliases is limited. Such properties can be used standalone as a yield alias only in a source query. In all other kinds of queries, component sub-properties must be yielded explicitly.

Yielding a component-typed property directly is supported for:

  • Money - sub-property amount is implicitly yielded instead.

For the sake of demonstration, assume the following entity:

class Invoice extends AbstractEntity {
  @IsProperty
  @MapTo
  RichText comment;

  @IsProperty
  @MapTo
  Money fee;
}

Properties comment and fee are modelled with component types, which means they cannot be directly yielded in a top-level query. Instead, each component sub-property must be yielded explicitly.

// Invalid query
q1 = select(Invoice.class)
  // equivalent to yield().prop("comment.searchText").as("comment")
  .yield().prop("comment").as("comment")
  // equivalent to yield().prop("fee.amount").as("fee")
  .yield().prop("fee").as("fee")
  .model();

// Valid query
q2 = select(Invoice.class)

  .yield().prop("comment.formattedText").as("comment.formattedText")
  .yield().prop("comment.coreText").as("comment.coreText")
  // Yielding searchText is optional
  .yield().prop("comment.searchText").as("comment.searchText")

  .yield().prop("fee.amount").as("fee.amount")

  .model();

Entities retrieved with query q2 will have properties comment and fee correctly initialised.

As described above, some component types have special support for being used as yield aliases directly. An outer query that uses a source query with such yields can refer to them via prop as is. I.e., property resolution works as expected in such cases.

sourceQ = select().
  yield().X.as("fee"). // (1)
  modelAsEntity(Invoice.class);

q = select(sourceQ).
  yield().beginExpr().prop("fee").mult().val(2).endExpr().as("fee.amount"). // (2)
  modelAsEntity(Invoice.class);

Source query sourceQ doesn't need to include .amount in the alias (1), and its enclosing query q can refer to fee (recall that prop("fee") will expand to prop("fee.amount")). The top-level query q, on the other hand, must specify the full component sub-property path (2) in the yield alias.

This limited form of support is useful for working with synthetic entities. Synthetic entity models are always interpreted as source queries, enabling the use of alias shortcuts described in this section.

Source queries

A source query is a query that is used as a source of another query.

Example:

select(
  // source query 1
  select(InventoryItem.class).yield().prop("price").as("cost").modelAsEntity(ReTransaction.class),
  // source query 2
  select(ReturnReceipt.class).yield().prop("refund").as("cost").modelAsEntity(ReTransaction.class)
)
.where().prop("cost").ge().val(100)
.modelAsEntity(ReTransaction.class)

If two or more source queries are used, they form a union.

A query that contains source queries is subject to the following constraints:

  • If two or more source queries are used, they must have the same number of yields and use the same set of yield aliases.

    The following examples demonstrate invalid queries:

    1. Different numbers of yields.

      select(
        // 2 yields
        select(InventoryItem.class)
          .yield().prop("price").as("cost")
          .yield().prop("id").as("id")
          .modelAsEntity(ReTransaction.class),
        // 1 yield
        select(ReturnReceipt.class)
          .yield().prop("refund").as("cost")
          .modelAsEntity(ReTransaction.class)
      )
      
    2. Different sets of yield aliases.

      select(
        // { "cost", "id" }
        select(InventoryItem.class)
          .yield().prop("price").as("cost")
          .yield().prop("id").as("id")
          .modelAsEntity(ReTransaction.class),
        // { "expense", "id" }
        select(ReturnReceipt.class)
          .yield().prop("price").as("expense")
          .yield().prop("id").as("id")
          .modelAsEntity(ReTransaction.class)
      )
      

Values and parameters

EQL queries can contain values (val()) and parameters (param()).

The set of types that can be used for values and parameters is constrained.

The following rules to apply to component types:

  • Money -- expanded to amount.
  • RichText -- cannot be used. One of its components should be used instead.

Other topics