Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Conditionals

CXL provides two conditional expression forms: if/then/else and match. Both are expressions – they return values and can be used anywhere an expression is expected.

If / then / else

The basic conditional expression:

if condition then value else alternative
$ cxl eval -e 'emit label = if amount > 100 then "high" else "low"' \
    --field amount=250
{
  "label": "high"
}

The else branch is optional. When omitted, records where the condition is false or null produce null. A null condition takes the else branch when there is one:

$ cxl eval -e 'emit bonus = if score > 90 then score * 0.1' \
    --field score=80
{
  "bonus": null
}

Branches of one numeric type

The two branches of an if must not be a decimal and a float, because the result would be a decimal on some rows and a float on others. Such an if does not compile:

cannot mix decimal and float without an explicit cast: the branches of this `if` are a decimal (`amount`) and a float (`price`); declare `price` a decimal in its Source schema, `type: decimal` in place of `type: float`, so the branches have one numeric type

The message gives one fix. When the float branch is a Source column, it is the column’s schema type: declare price with type: decimal in place of type: float, and the reader parses its text exactly, so both branches are decimals holding the values the file holds. (A JSON number read into a decimal column is still parsed through a float first; see #1299.) When the float branch is computed rather than read from a Source column, the fix converts the decimal branch with .to_float() instead, accepting binary float precision. An integer branch is fine beside either: it widens exactly into the decimal or float.

Chained conditionals

Chain multiple conditions with else if:

$ cxl eval -e 'emit tier = if amount > 1000 then "platinum"
    else if amount > 500 then "gold"
    else if amount > 100 then "silver"
    else "bronze"' \
    --field amount=750
{
  "tier": "gold"
}

Nested usage

Since if/then/else is an expression, it can be used inside other expressions:

$ cxl eval -e 'emit price = base * (if member then 0.8 else 1.0)' \
    --field base=100 \
    --field member=true
{
  "price": 80.0
}

Match

The match expression provides pattern matching. It comes in two forms: value matching (with a subject) and condition matching (without a subject).

Value form (with subject)

Match a subject expression against literal patterns:

match subject {
  pattern1 => result1,
  pattern2 => result2,
  _ => default
}
$ cxl eval -e 'emit label = match status {
    "A" => "Active",
    "I" => "Inactive",
    "P" => "Pending",
    _ => "Unknown"
  }' \
    --field status=A
{
  "label": "Active"
}

The wildcard _ is the catch-all arm. It matches any value not covered by preceding arms.

Condition form (without subject)

When no subject is provided, each arm’s pattern is evaluated as a boolean condition, and the first arm whose condition is exactly true wins. An arm whose condition is null is skipped:

match {
  condition1 => result1,
  condition2 => result2,
  _ => default
}
$ cxl eval -e 'emit tier = match {
    amount > 1000 => "high",
    amount > 100 => "medium",
    _ => "low"
  }' \
    --field amount=500
{
  "tier": "medium"
}

Practical examples

Tiered pricing:

emit discount = match {
  qty >= 1000 => 0.25,
  qty >= 100  => 0.15,
  qty >= 10   => 0.05,
  _           => 0.0
}

Status code mapping:

emit status_text = match http_code {
  200 => "OK",
  201 => "Created",
  400 => "Bad Request",
  404 => "Not Found",
  500 => "Internal Server Error",
  _   => "HTTP ".concat(http_code.to_string())
}

Region classification:

emit region = match country {
  "US" => "North America",
  "CA" => "North America",
  "MX" => "North America",
  "GB" => "Europe",
  "DE" => "Europe",
  "FR" => "Europe",
  _    => "Other"
}

Arms of one numeric type

As with if, the arms of a match must not include both a decimal and a float. The error names the first decimal arm and the first float arm, counted from 1, or the field when an arm is a bare field, and gives the same one fix: the Source schema type when the float arm is a float column, otherwise .to_float() on the decimal arm.

Match arms are evaluated in order

The first matching arm wins. Place more specific conditions before general ones:

# Correct: specific before general
emit category = match {
  amount > 10000 => "enterprise",
  amount > 1000  => "business",
  _              => "personal"
}

# Wrong: first arm always matches
emit category = match {
  amount > 0     => "personal",    # catches everything positive
  amount > 1000  => "business",    # never reached
  amount > 10000 => "enterprise",  # never reached
  _              => "unknown"
}