Skip to content

AEL control structures and postfix flags

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Reference page: part of the AEL reference. Covers when/let control structures and the postfix flags that modify write and read terminals. See Applies to on the overview page for SDK and Database version requirements.

Conditional: when

when works like an IF … THEN … ELSIF … ELSE chain in other languages: it evaluates each condition in order and returns the value paired with the first one that’s true.

when (cond1 => val1, cond2 => val2, default => val3)
PartRequirement
Conditions (condN)TRILEAN
Actions (valN)All branches the same type, except the reserved literals unknown and error (explained in the next paragraph)
default => …Required fallback branch

Result type is the unified action type of all non-unknown / non-error branches.

when ($.tier == 1 => 'gold', $.tier == 2 => 'silver', default => 'bronze')

Reserved trilean value literals: the words unknown and error are value literals, not exceptions, parse failures, or shorthand for “throw”. Both spellings are synonymous: they may appear on any branch regardless of the types on other branches, and at evaluation time both produce the TRILEAN value unknown. Typical use: default => unknown (or default => error) when no condition matches and the expression should yield an indeterminate result rather than a typed default. This is distinct from a condition or comparison returning unknown because a bin or path operand is absent (see TRILEAN).

/* Parse error — branch result types must agree (INT default vs STRING arms) */
when ($.tier == 1 => 'gold',
$.tier == 2 => 'silver',
$.tier == 3 => 'bronze',
default => 0
)
/* Valid — `unknown` and `error` are interchangeable value literals */
when ($.tier == 1 => 'gold',
$.tier == 2 => 'silver',
$.tier == 3 => 'bronze',
default => unknown /* same runtime value as `default => error` */
)

Variable binding: let, then

let (var1 = expr1, var2 = expr2) then (bodyExpr)

References to bound variables use ${varName}. Variable types are inferred from their initializer expressions. Variables can reference earlier variables in the same binding list:

let (total = $.price * $.qty, discount = ${total} / 10) then (${total} - ${discount} > 400)

Float literals in let expressions cause a parse error.

Use only integer arithmetic in let expressions unless all operands are explicitly floats:

/* Parse error (cannot compare: FLOAT vs INT): INT combined with a float literal */
let (total = $.price * $.qty, tax = ${total} * 0.1) then (${total} + ${tax} > 900)
/* Parse error (type mismatch: INT vs FLOAT): INT bin multiplied by a float literal */
$.intBin:INT * 0.1 > 5
/* Fixed: keep everything INT (scale by 1000 or use integer percentages) */
let (total = $.price * $.qty, tax_pct = 10, tax = ${total} / ${tax_pct}) then (${total} + ${tax} > 990)

Type consistency in let bindings

To avoid this:

  • Pin every bin used in let arithmetic to an explicit type (:INT or :FLOAT), or
  • Use .toFloat() / .toInt() explicitly to cast before combining.

(${name}).toFloat() / (${name}).toInt() follow the same explicit-cast rules as casting a bin path (see Types and type suffixes). A let variable reference cannot have functions called on it, so it must be parenthesized before calling a cast method: ${total}.toFloat() is a syntax error, but (${total}).toFloat() compiles.

/* All INT arithmetic: */
let (total = $.price * $.qty, discount = ${total} / 10) then (${total} - ${discount} > 400)
/* Mixed types (NOTE: INT * FLOAT silently excludes every record in a filter expression, but is a runtime error in a read/write expression, if types don't align): */
let (discount_rate = 0.13, total = $.price * $.qty, discount = ${total} * $.discount_rate) then (${total} - ${discount} > 400)
/* Fixed with explicit casts and pinned types: */
let (total = $.price:INT * $.qty, discount = (${total}).toFloat() * $.discount_rate:FLOAT) then ((${total}).toFloat() - ${discount} > 400.0)

Postfix flags

Attach postfix flags immediately after ) on path terminals, not as named parameters inside ():

FlagValid onDescription
:NO_FAILCollection data type (CDT) writes; modify(), remove(); pathed string modify; hllInit, hllAdd; all BLOB bit modify opsAbsent-path / policy tolerance — see :NO_FAIL semantics
:PARTIALMap putItems, insertItems, updateItems; list appendItems, insertItems (index path); BLOB bitRemove, bitSet, bitOr, bitXor, bitAnd, bitNot, bitLshift, bitRshiftOn bulk CDT writes: apply entries that succeed even when others fail; implies :NO_FAIL. On BLOB bit ops: clip the op to the blob end when the range extends past the end; does not imply :NO_FAIL
:CREATE_ONLYhllInit, hllAdd; bitResize, bitInsertFail if the operation would modify an existing bin
:UPDATE_ONLYhllInit; all BLOB bit modify opsFail if the operation would create a new bin. Parse error on hllAdd
:ADD_UNIQUEList append, appendItems, insert, insertItems, setTo, addFail (or skip under :NO_FAIL) when an element equals one already in the list. A duplicate within the same bulk payload reports OP_NOT_APPLICABLE; a duplicate against existing list content reports ELEMENT_EXISTS
:DROP_DUPSsort()Drop duplicate elements while sorting
:REVERSEgetIndexes(), getRanks()Reverse index/rank direction
:UNORDEREDgetMaps()Unordered return map shape on getMaps() only — distinct from the map literal and path-segment create-order uses of :UNORDERED; see Literals
:PERSIST_INDEXBin root onlyPersist top-level map index on create
:UNSORTED_PADPath segment that materializes a missing list — not valid on modify() or remove() pathsCreate an unsorted list with elements allowed to be inserted anywhere past the end of the list when materializing a missing container; see Bounded list writes

Terminal-kind rule (create-order vs. :NO_FAIL vs. reads): create-order suffixes are write-only and allowed only on write terminals that can create containers. :NO_FAIL is write-only. Read terminals take neither create-order suffixes nor :NO_FAIL. :UNSORTED_PAD is a create-order suffix.

Read paths when a segment is absent: reads cannot use :NO_FAIL: a missing CDT context step fails under strict navigation (see Record and bin prefix). Use exists() to test presence instead — it returns false when the path does not match.

/* :REVERSE reverses the index/rank direction of the returned list */
$.scores:LIST.[1:3].getIndexes():REVERSE
/* :PERSIST_INDEX persists the top-level map's key index so subsequent
key-based lookups on this bin can use it, instead of rebuilding it each time.
It's valid on the bin root only, so it must come before any path segments. */
$.m:KEY_ORDERED:PERSIST_INDEX.k.setTo(1)
$.optional.field.setTo('value'):NO_FAIL
$.tags.append('x'):ADD_UNIQUE
$.tags.sort():DROP_DUPS
$.m.{@a: d}.getMaps():UNORDERED
$.m:MAP.insertItems({a: 1, b: 2}):PARTIAL
$.h.hllInit(indexBits: 14):CREATE_ONLY

:PARTIAL trades all-or-nothing atomicity for tolerance of individual failures within a bulk write. For example, consider using a list as a set with :ADD_UNIQUE. To add several candidate elements where some may already be present, $.tags.appendItems(candidates):ADD_UNIQUE:PARTIAL inserts only the elements that aren’t already in the list and skips the duplicates, instead of failing the whole call on the first one it finds. Without :PARTIAL or :NO_FAIL, a bulk write fails atomically on the first error, which is the right choice when your application requires all-or-nothing semantics. :NO_FAILis implied by:PARTIAL` in this context.

:NO_FAIL semantics

The flag has two runtime axes; both are narrower than “suppress any failure”:

AxisValid onEffect when set
Path-level (absent CTX)CDT writes; modify(), remove(); pathed string modifyA CDT context segment on the compiled path is missing in the bin → no-op; the original bin is left unchanged
Bin-level (create/update policy)hllInit, hllAdd; BLOB bit modify opsA :CREATE_ONLY / :UPDATE_ONLY (or related type) conflict on the bin → no-op instead of failing

:NO_FAIL does not:

  • Suppress parse errors (including invalid flag placement on a terminal — AEL rejects nonsensical postfix at compile time).
  • Suppress read-terminal failures.
  • Suppress op-specific failures unless a separate mechanism applies (for example BLOB :PARTIAL clips a range).

:PARTIAL and :NO_FAIL: on bulk CDT ops (putItems, insertItems, updateItems, appendItems, list insertItems), :PARTIAL requires tolerant failure and automatically applies :NO_FAIL at AEL compile time. Without :PARTIAL, bulk CDT ops fail atomically (one failed entry aborts the entire call). On BLOB bit modify ops, :PARTIAL and :NO_FAIL are independent: :PARTIAL clips to the blob end, and :NO_FAIL suppresses create/update flag conflicts. Write both on BLOB when needed, for example bitSet(…):PARTIAL:NO_FAIL. On bulk CDT, explicit :NO_FAIL with :PARTIAL is redundant.

:NO_FAIL restrictions, summarized:

  • Valid only on writes, never on read terminals.
  • Applies to: absent-context container creation on a write path (such as the earlier $.optional.field example, where optional doesn’t yet exist), BLOB/HLL bin create/update policy, and pathed STRING modify functions (not a bare bin or (expr) receiver).
  • Does not mean “suppress any failure” — it tolerates one specific failure condition for its context.

The Developer SDK splits :NO_FAIL across the same two axes, as separate builder options on operation-expression builders: the path-level axis (absent CDT context, an expression resolving to unknown or a non-bin type) maps to ignoreEvalFailure() / ignore_eval_failure=True, and the bin-level axis (a :CREATE_ONLY / :UPDATE_ONLY create/update policy conflict) maps to ignoreOpFailure() / ignore_op_failure=True. The two are independent and can be combined on the same operation, for example opt -> opt.ignoreOpFailure().ignoreEvalFailure() in Java.

Next steps