---
title: "AEL operators"
description: "AEL reference: comparison, in, regex match, logical operators with TRILEAN truth tables, arithmetic, integer bitwise operators, and operator precedence."
---

# AEL operators

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Reference page: part of the [AEL reference](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference). Covers all AEL operators and their precedence. See [Applies to](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference#applies-to) on the overview page for SDK and Database version requirements.

## Comparison operators

```plaintext
$.age > 21

$.status == 'active'

$.price != 0
```

| Operator | Operand types | Result |
| --- | --- | --- |
| `==`, `!=`, `<`, `<=`, `>`, `>=` | Same type both sides | `TRILEAN` |
| `expr in listExpr` | Right side evaluates to `LIST` | `TRILEAN` |
| `stringExpr =~ /pattern/[/flags]` | Left side `STRING`; Perl-compatible regex | `TRILEAN` |

Both sides can be bins. Comparing two bins with no literal on either side requires an explicit `:TYPE` suffix on at least one side:

```plaintext
$.binA:INT > $.binB
```

Comparisons are not chainable (`a < b < c` is a parse error). Literals may appear on either side.

### The `in` operator

```plaintext
$.name in ["Bob", "Mary", "Richard"]

"gold" in $.allowedTiers
```

The right-hand side must evaluate to a `LIST`; the operator tests whether the left-hand value equals any list element. It does not test map keys or map values directly — use [selectors](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/selectors) or `exists()` for that. The left operand’s type must be resolved by the ordinary strict-typing rules (see [Type inference](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/type-inference-and-limits#type-inference-summary)); for example, `$.x in $.other` with neither side pinned is a parse error.

### Regex match

Regex match (left operand must be `STRING`). See [Regex literals](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/type-functions#regex-literals) for the pattern/flag syntax:

```plaintext
$.email =~ /^[^@]+@[^@]+\.[^@]+$/

$.sku.trim().upper() =~ /^[A-Z]{3}-\d{4}$/
```

The `=~` operator is the regex match form in AEL. The pattern must appear as a regex literal in the expression (`/…/` or `/…/flags`); it cannot be taken from a bin or variable. For example: `$.name =~ /Mike|Michael .*/` or `@key =~ /pen.*/`.

## Logical operators

```plaintext
$.age > 21 and $.status == 'active'

$.role == 'admin' or $.role == 'super'

not($.age > 65)

exclusive($.a > 10, $.b > 5)
```

| Form | Operands | Result |
| --- | --- | --- |
| `a and b` | `TRILEAN` | `TRILEAN` |
| `a or b` | `TRILEAN` | `TRILEAN` |
| `not(expr)` | `TRILEAN` | `TRILEAN` |
| `exclusive(a, b, …)` | `TRILEAN` (varargs) | `TRILEAN` — true iff exactly one operand is true |

`and` binds tighter than `or`. Use parentheses to clarify:

```plaintext
$.a > 1 and ($.b > 2 or $.c > 3)
```

::: note
Boolean-context results in AEL are `TRILEAN`, not `BOOL`: they can be `true`, `false`, or `unknown` (for example when a compared path doesn’t resolve). Comparisons, `in`, `=~`, `exists()`, `geoCompare()`, and predicates like `startsWith()` / `endsWith()` all return `TRILEAN`. `BOOL` remains the particle-type constant for literal `true` / `false` values and `:BOOL` typing.
:::

### TRILEAN truth tables

Trilean AND operation truth table

| _`AND`_ | `true` | `false` | `unknown` |
| --- | --- | --- | --- |
| `true` | true | false | unknown |
| `false` | false | false | false |
| `unknown` | unknown | false | unknown |

Trilean OR operation truth table

| _`OR`_ | `true` | `false` | `unknown` |
| --- | --- | --- | --- |
| `true` | true | true | true |
| `false` | true | false | unknown |
| `unknown` | true | unknown | unknown |

`not(a)`: `true` → `false`; `false` → `true`; `unknown` → `unknown`.

## Arithmetic

```plaintext
($.price * $.qty) > 1000

($.apples + 5) > 10

abs($.score - 50) < 10

max($.a, $.b, $.c) > 100

$.value % 2 == 0

$.base:FLOAT ** 2.0 > 100.0
```

| Operator | Operands | Result | Notes |
| --- | --- | --- | --- |
| `+` | `INT` or `FLOAT` (matching) | Same numeric type | Numeric addition |
| `+` | `STRING` (matching) | `STRING` | String concatenation |
| `-` | `INT` or `FLOAT` (matching) | Same numeric type |  |
| `*`, `/`, `%` | `INT` or `FLOAT` (matching) | Same numeric type | `%` integer only |
| `**` | `FLOAT` | `FLOAT` | Right-associative |

Type-directed `+`: when both operands are numeric, `+` adds (`12 + 13`). When both operands are `STRING`, `+` concatenates (`$.first + ' ' + $.last`, `"Mr. " + $.name`). A string literal anywhere in a `+` chain pins the chain to `STRING`; an all-bin string chain with no anchor is a parse error. Mixing incompatible types (for example `$.a:INT + "x"`) is a type error. There is no standalone `concat()`, `append()`, or `prepend()` string method; use `+` or `splice()` (see [String path functions](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/type-functions#string-path-functions)).

Both operands must be the same type. Use `toInt()` / `toFloat()` to convert between numeric types, or to cast a numerically-valued bin before comparison. `toInt()` / `toFloat()` need a resolved receiver: a bare bin path with no `:INT`/`:FLOAT` pin, and a bare numeric literal, are both unresolved as method receivers, so pin the bin or parenthesize the literal first:

```plaintext
$.intBin:INT + $.floatBin:FLOAT.toInt()

$.count:INT.toFloat() > 3.14

(3.14).toInt() == 3
```

`toInt()` / `toFloat()` converts between the two numeric types (`INT` ↔ `FLOAT`) and also parses numeric text out of `STRING` bins. A bin stored as the string `"42"` can be compared numerically with `.toInt()`.

Integer division and `%`: when both operands of `/` are `INT` bins, AEL performs true integer division, truncating any remainder, the same as most programming languages: `7 / 2` evaluates to `3` (an `INT`), not `3.5`. `%` (modulo) also requires `INT` operands on both sides and always returns an `INT` remainder. To get a fractional result instead, cast at least one operand with `.toFloat()` before dividing: `$.a.toFloat() / $.b`.

## Integer bitwise

Integer bitwise operators apply to whole 64-bit `INT` values (not BLOB bit ranges):

```plaintext
$.flags & 0xFF == 0x01

$.mask | $.extra

~$.bits

$.value << 2

$.value >> 1

$.value >>> 1
```

| Operator | Operands | Result |
| --- | --- | --- |
| `&`, `^`, `|` | `INT` | `INT` |
| `~expr` | `INT` | `INT` |
| `<<`, `>>`, `>>>` | `INT` | `INT` |

`>>` is arithmetic shift; `>>>` is logical (zero-fill). `&`, `^`, and `|` share a single precedence level and are left-associative, the same as the [operator precedence table](#operator-precedence) shows: `a | b & c` evaluates as `(a | b) & c`, not `a | (b & c)`. Parenthesize explicitly when you need `&` or `^` to bind tighter than `|`.

## Operator precedence

Lowest to highest binding. `&`, `^`, and `|` share one precedence level and are left-associative:

| Level | Operators / forms |
| --- | --- |
| 1 | `or` |
| 2 | `and` |
| 3 | `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `=~` |
| 4 | `&`, `|`, `^` (bitwise, left-associative) |
| 5 | `<<`, `>>`, `>>>` |
| 6 | `+`, `-` |
| 7 | `*`, `/`, `%` |
| 8 | `**` |
| 9 | Unary `~`, `not(…)`, unary `-`, unary `+` |
| 10 | Primary: literals, paths, calls, `(…)` |

Comparisons are not chainable (`a < b < c` is a parse error).

## Next steps

-   [Functions and terminals](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/functions) — record metadata, standalone functions, and path terminals
-   [Control structures and postfix flags](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/control-and-flags) — `when`, `let`, and write-policy flags
-   [Type inference and limits](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference/type-inference-and-limits) — how AEL resolves operand types