← Files ConviuARCHIVED FILE

skills/conviu-agent/references/fql.md

8.15 KB · Oct 9, 2026 · 18:02 UTC

↓ Download file

# FQL — filter & condition syntax

**FQL** (Feed Query Language) is the small query string Conviu uses to select items. It is **not
SQL** — `SELECT`, `WHERE`, `LIKE` and bare `=` do not exist, and `%` is **not** a wildcard (the
wildcard is `*`; in an export rule's `conditions` a `%name%` token is a Conviu variable, see below).
You write FQL wherever a tool takes:

- a `filter` — on list tools: `list_items`, `list_data_sources`, `list_data_writer_jobs`
- `conditions` — on export rules

## Discover the fields first

A term addresses a **field** (column). Never guess field names — they depend on the data. Get the
real ones from actual data: call `list_items` (no filter, small limit) and read the keys of the
returned item `data`, plus `customData` and `variables`. Common product fields include `title`,
`brand`, `salePrice`, `inStock`; two special paths are always available: `dataSource.uid` (which
import an item came from) and `approval` (its approval status). Field paths may be nested with a dot
(`dataSource.uid`).

## Values: quoting, escaping, case

- Every value is wrapped in **double quotes**: `brand:"Nike"` — even numbers and booleans
  (`salePrice:["100" TO *]`, `inStock:"true"`).
- Escape an inner double quote as `\"` (and a literal backslash as `\\`).
- Matching is **case-insensitive by default**. Append **`#s`** for a case-sensitive comparison. The
  flag attaches to the value part and works on equality, the text matches, and sets:
  `brand:"Nike"#s`, `title:"*shoe*"#s`, `brand:("Nike;Adidas"#s)`.

## Operators

`field` is the column. Each row shows the exact FQL the operator produces.

### Equality

| Intent | FQL |
|---|---|
| Equals | `field:"value"` |
| Not equals | `NOT field:"value"` |

### Text match (TEXT / ARRAY fields)

| Intent | FQL |
|---|---|
| Contains | `field:"*value*"` |
| Not contains | `NOT field:"*value*"` |
| Starts with | `field:"value*"` |
| Ends with | `field:"*value"` |
| Not starts / not ends | `NOT field:"value*"` / `NOT field:"*value"` |

### Set — value in a list (OR within one field)

Values are separated by **semicolons** (`;`), inside quotes and parentheses:

| Intent | FQL |
|---|---|
| In a set | `field:("a;b;c")` |
| Not in a set | `NOT field:("a;b;c")` |

### Regular expression (TEXT / ARRAY fields)

The pattern is wrapped in tildes inside the quotes:

| Intent | FQL |
|---|---|
| Matches regex | `field:"~pattern~"` |
| Doesn't match | `NOT field:"~pattern~"` |

### Numeric / date comparisons (open-ended ranges)

The meaningful bracket sits on the **value** side: `[` / `]` inclusive, `{` / `}` exclusive; `*` is
the open end.

| Intent | FQL |
|---|---|
| ≥ x | `field:["x" TO *]` |
| ≤ x | `field:[* TO "x"]` |
| > x | `field:{"x" TO *]` |
| < x | `field:[* TO "x"}` |

### Between two bounds

| Intent | FQL |
|---|---|
| a ≤ v ≤ b (inclusive) | `field:["a" TO "b"]` |
| a < v < b (exclusive) | `field:{"a" TO "b"}` |
| a ≤ v < b | `field:["a" TO "b"}` |
| a < v ≤ b | `field:{"a" TO "b"]` |

### Advanced: length modifier `#L`

Compares the value's **character length** rather than the value itself, via a trailing `#L`:
`field:"5"#L` (length equals 5), `field:{"5" TO *]#L` (length greater than 5),
`field:[* TO "5"}#L` (length shorter than 5). Use it only when you specifically need to filter by
how long a text field is.

## Combining terms

Join conditions with **`AND`** and **`OR`**; negate a single term by prefixing its field with
**`NOT`** (`NOT brand:"Nike"`). The canonical shape — what Conviu's own filter builder emits — is:
OR-alternatives grouped in parentheses, **every group** wrapped and joined by `AND`:

```
(brand:"Nike" OR brand:"Adidas") AND (salePrice:["100" TO "500"]) AND (inStock:"true")
```

The rules Conviu's builder follows:
- **A lone condition stays bare** — no parentheses needed: `brand:"Nike"`.
- **Combining two or more? Wrap each group in `(...)`** and join with ` AND ` (or ` OR `) — including
  groups that hold just one condition. That's why `(inStock:"true")` above is parenthesized.
- `AND` binds groups of `OR` alternatives — put one field's OR options together inside one set of
  parentheses.

Filters without the per-group parentheses still parse, but emit the canonical form above: it stays
unambiguous as conditions get added.

## Worked examples

Combined filters use the canonical per-group parentheses; single-condition filters stay bare.

- Nike or Adidas, priced 100–500, in stock:
  `(brand:"Nike" OR brand:"Adidas") AND (salePrice:["100" TO "500"]) AND (inStock:"true")`
- More expensive than 1000 (strictly greater), in stock:
  `(salePrice:{"1000" TO *]) AND (inStock:"true")`
- Strictly between 100 and 500, brand exactly "Nike" (case-sensitive):
  `(salePrice:{"100" TO "500"}) AND (brand:"Nike"#s)`
- From one specific import, awaiting review:
  `(dataSource.uid:"<uid32>") AND (approval:"pending")`
- Title mentions "sleva" but is NOT clearance:
  `(title:"*sleva*") AND (NOT title:"*výprodej*")`
- Title longer than 70 characters — `filter` only, see the note on `#L` below (single condition, no
  parentheses): `title:{"70" TO *]#L`
- SKU matching an exact pattern (regex), case-sensitive:
  `sku:"~^AB[0-9]{4}$~"#s`
- One of several ids:
  `id:("101;102;103")`

## Fields inside an export rule's `conditions`

A rule's `conditions` are FQL too, but the **field** is written differently than in a `filter`, and
there are exactly two valid forms. Anything else is accepted by the server, stored happily, and then
**never matches a single item** — the rule looks fine and silently does nothing.

| Form | Reads | Where the exact spelling comes from |
|---|---|---|
| `%variable%` | the item's **source** data | `variables` in `get_data_writer_job` / `get_data_writer_job_rule` — copy `name` verbatim, percent signs included |
| `'Full > path'` | the **rendered output element**, i.e. after mapping into the format and after every rule with a lower `position` | `fields` in `get_data_writer_job` — an exact map key, in single quotes, never shortened to its last segment |

Prefer `%variable%`: it reads the source data, so it does not depend on the output format or on the
order rules run in. Reach for `'Full > path'` only when the question really is about the finished
feed, or when no variable fits.

```
(%price-vat%:{"250" TO *]) AND (%productname%:"*protein*")
'item > g:title':"*Akce*"
```

A bare name (`price-vat`, `title`) is the mistake to watch for — it is neither form, so it resolves
to nothing. `list_data_queries` does not return either catalogue; `get_data_writer_job` does.

Three further limits apply to rule `conditions` and not to `filter`:

- **No closed ranges.** Write `["500" TO *]` and `[* TO "1000"}` as two separate terms instead of
  `["500" TO "1000"]`.
- **`#L` and `#W` are ignored.** They work in a `filter`, but a rule's condition evaluator reads
  only the `#s` flag.
- **Keep the canonical shape.** Wrap every AND-group in parentheses, even a single one, and keep the
  spacing exactly as shown above — Conviu's visual rule editor can only display a condition it can
  re-encode character for character, and quietly drops any term it cannot.

## Common mistakes to avoid

- **SQL habits.** `LIKE`, `WHERE` and bare `=` don't exist — use `field:"value"` and the wildcards
  above. `%` is not a wildcard either; the only meaning of `%` in FQL is a `%variable%` token inside
  an export rule's `conditions`.
- **Missing quotes.** Every value is double-quoted, including numbers and booleans.
- **Guessing field names.** Verify against real items first — a filter over a non-existent field matches nothing.
- **Wrong range bracket.** `>` needs the exclusive `{`; `≥` needs the inclusive `[`. Mixing them shifts the boundary by one.
- **Commas in a set.** The set separator is `;`, not `,`.
- **`#s` in the wrong place.** For a set it goes inside the parentheses (`field:("a;b"#s)`); elsewhere it trails the closing quote (`field:"value"#s`).

## Sorting (alongside filters)

List tools also accept a `sort`, separate from the FQL filter. For `list_data_sources` and
`list_data_writer_jobs` it's a single object `{ field, order }`; for `list_items` it's an array of
such objects. `order` is `"ASC"` or `"DESC"`, e.g. `{ "field": "lastImportedAt", "order": "DESC" }`.
The filter selects; the sort orders.

SHA-256: 322bc019fcc4490e4456d1de0e4ad45b9c7f5a1c7fe3997952ad3c0138dd01c3