Skip to main content

SQONs In Detail

SQON is a JSON-based filter language Overture uses to describe query logic in a backend-neutral way, while retaining human readability and portability. This page focuses on the shape of a SQON itself: what nodes exist, what operators are supported, what aliases are accepted, and which edge cases matter when generating SQON programmatically.

Example: Flat Filter vs. SQON

Flat Filter Approach:

A typical flat filter uses simple key-value pairs with implicit AND logic:

{
"province": "Ontario",
"age": "20-29"
}

General limitations of a flat filter:

  • Can only express AND logic (all conditions must match)
  • Cannot express OR relationships between filters
  • Cannot nest conditions or create complex boolean expressions

SQON Approach:

SQON can express the same filter explicitly:

{
"op": "and",
"content": [
{
"op": "in",
"content": {
"fieldName": "province",
"value": ["Ontario"]
}
},
{
"op": "in",
"content": {
"fieldName": "age",
"value": ["20-29"]
}
}
]
}

Mental Model​

Visualize a SQON as a "tree" of nested operations, that may contain one of two kinds of elements:

  • Leaf nodes apply an operator to one or more field values. e.g. age is between 0 and 100.
  • Group nodes combine other SQON nodes with boolean logic. e.g. the AND in "and eye color is brown".

A SQON may start at either level:

  • a single leaf node
  • a group node containing one or more child nodes

Group Nodes​

Group nodes combine child SQON nodes. e.g. "filter X" AND "filter Y"
{
"op": "and",
"content": [
{
"op": "in",
"content": {
"fieldName": "fruit.color",
"value": ["red"]
}
},
{
"op": "gte",
"content": {
"fieldName": "fruit.weight_grams",
"value": 100
}
}
]
}

This results in:

  • fruit color is red
  • and fruit weight is at least 100 grams

Supported group operators​

  • and
  • or
  • not

Group shape​

{
"op": "and | or | not",
"content": ["SQON", "SQON", "..."]
}

Leaf Nodes​

Leaf nodes describe a field-level filter. e.g. "value X" IN "field Y"
{
"op": "in",
"content": {
"fieldName": "fruit.color",
"value": ["red"]
}
}

This results in:

  • fruit color is red

Most leaf nodes use:

  • fieldName
  • value

The wildcard operator is the exception and instead uses fieldNames (plural).

Field Operators​

SQONs can apply several kinds of filtering to fields and values:

  • membership answers questions like "is this value in the allowed set?" or "is it excluded from that set?"
  • range compares values against bounds such as greater than, less than, or between two endpoints
  • wildcard performs a case-insensitive substring match across one or more fields using ES/OS wildcard queries

Membership-style operators​

  • in
  • not-in
  • some-not-in
  • all
Example:
{
"op": "in",
"content": {
"fieldName": "fruit.color",
"value": ["red", "green"]
}
}

This results in:

  • fruit color is red or green

Range-style operators​

  • gt
  • gte
  • lt
  • lte
  • between
Example:
{
"op": "between",
"content": {
"fieldName": "fruit.weight_grams",
"value": [100, 200]
}
}

This results in:

  • fruit weight is between 100 and 200 grams

Wildcard operator​

  • wildcard
Example:
{
"op": "wildcard",
"content": {
"fieldNames": ["fruit.name", "fruit.nickname"],
"value": "*app*"
}
}

This results in:

  • case-insensitive substring match for app in either fruit.name or fruit.nickname

The wildcard operator translates to an ES/OS wildcard query with case_insensitive: true. Use * in the value to express substring patterns (e.g. *apple*, apple*, *apple). This is distinct from fuzzy (edit-distance) matching: it finds substrings, not approximate terms.

Accepted Operator Aliases​

Arranger accepts several shorthand aliases in addition to canonical operators.

AliasCanonical Operator
=in
==in
===in
!=not-in
!==not-in
>gt
>=gte
<lt
<=lte
filterwildcard

For interoperability, the canonical operator names are always preferred when generating new SQONs.

Pivot​

A SQON node may also include pivot.

Consider a set of records shaped like this:

[
{
"basket_name": "Andy's basket",
"items": [
{ "name": "apple", "color": "red" },
{ "name": "pear", "color": "yellow" }
]
},
{
"basket_name": "Max's basket",
"items": [
{ "name": "apple", "color": "green" },
{ "name": "cherry", "color": "red" }
]
}
]

Now imagine we want to express:

  • there exists an item whose name is apple
  • and that same item is red
Without a pivot
{
"op": "and",
"content": [
{
"op": "in",
"content": {
"fieldName": "items.name",
"value": ["apple"]
}
},
{
"op": "in",
"content": {
"fieldName": "items.color",
"value": ["red"]
}
}
]
}

This can be read as:

  • some item has the name apple
  • and some item has the color red

That may accidentally match across different nested objects, providing green apples and red cherries.

With a pivot
{
"op": "and",
"pivot": "items",
"content": [
{
"op": "in",
"content": {
"fieldName": "items.name",
"value": ["apple"]
}
},
{
"op": "in",
"content": {
"fieldName": "items.color",
"value": ["red"]
}
}
]
}

The pivot is used to anchor a filter to a nested path. This matters when Arranger translates SQON into Elasticsearch nested queries.

This results in:

  • look within the items nested path, and
  • require the apple and red conditions to be true for the same nested item

So the practical difference is:

  • without pivot: the conditions may be satisfied by different nested rows
  • with pivot: the conditions are scoped to the same nested row

In practice:

  • most simple SQONs omit pivot
  • nested aggregations and nested field filtering are where pivot becomes important
  • pivot may appear on either leaf or group nodes

A pivot can still be rejected later at runtime if it does not match a valid nested field path for the active catalogue.

Current Accepted Value Shapes​

The current Arranger SQON schema accepts:

  • membership operators: scalar or array
  • range operators: scalar or array
  • between: scalar or array with at least 2 items
  • filter: string

That reflects current compatibility behavior, not necessarily the final ideal shape. In particular:

  • range operators currently tolerate arrays even though scalar values are usually clearer
  • between currently accepts arrays longer than 2, and downstream logic may reduce them to a min/max pair

Programmatic clients should prefer the clearer forms:

  • gt, gte, lt, lte: single scalar value
  • between: exactly 2 values

Extra Keys​

Arranger currently accepts but ignores extra keys on SQON nodes and content objects.

That means this is structurally valid today:

{
"op": "in",
"content": {
"fieldName": "fruit.color",
"value": ["red"],
"extraContent": true
},
"extraTopLevel": "ignored"
}

This results in:

  • fruit color is red
  • and the extra keys are ignored by current SQON validation

Important Edge Cases​

These are worth handling explicitly when generating SQON in other systems such as MCP servers.

Falsy values can still be valid​

These are valid SQON values and should not be treated as missing:

  • 0
  • ""
Examples:
{
"op": "gte",
"content": {
"fieldName": "fruit.weight_grams",
"value": 0
}
}

This results in:

  • fruit weight is at least 0 grams
{
"op": "in",
"content": {
"fieldName": "fruit.label",
"value": ""
}
}

This results in:

  • fruit label is the empty string

Special Arranger values​

Some values have special downstream meaning in Arranger.

Examples include:

  • set_id:<id>
  • __missing__
  • wildcard-like strings such as ABC*

These are still ordinary SQON values structurally, but Arranger may compile them into specialized Elasticsearch queries.

Introspection​

The GET /introspection/sqon endpoint returns the SQON JSON Schema and operator metadata for this server: combination operators, field operators with value types and applicability, and accepted aliases. Use it to validate or describe SQON structure independently of any specific catalogue.

For full introspection API documentation: including catalogue discovery and per-catalogue field listings: see Introspection API.