# WBQL - Wallboard Query Language

Filtering syntax for `search` parameter. Case-sensitive.

## Value Operators

| Symbol | Meaning | Example |
|--------|---------|---------|
| `:` | Contains (strings) / equals (others) | `name:lobby` |
| `=` | Exact match | `deviceStatus=ONLINE` |
| `≠` | Not equal | `type≠SCREEN` |
| `∉` | Not contains (strings) | `name∉test` |
| `^` | Starts with | `name^Device` |
| `>` `≥` `<` `≤` | Numeric/date comparison | `brightnessLevel>50` |
| `∈` | In set (encode value separators) | `type∈SCREEN%2CTABLET` |

Note: `tags:value` checks array contains (special case).

## Logical Operators

| Symbol | Meaning | Example |
|--------|---------|---------|
| `,` | AND | `status=ONLINE,type=SCREEN` |
| `\|` | OR | `name=A\|name=B` |
| `()` | Grouping | `(status=ONLINE\|status=OFFLINE),platform=ANDROID` |

**⚠️ OR binds tighter than AND** — `a:1|b:2,c:3` = `(a:1 OR b:2) AND c:3`

## Special Values

| Value | Meaning | Example |
|-------|---------|---------|
| `=NULL` | Is null / collection empty | `parentId=NULL`, `teamAssignments=NULL` |
| `=!NULL` | Not null / collection not empty | `content=!NULL` |
| `=true` / `=false` | Boolean | `enabled=true` |

`NULL` works on JPA collections — useful for checking empty relations (e.g., `teamAssignments=NULL`).

## Advanced Syntax

**Nested fields:** `content.name:welcome`, `teamAssignments.team.id=abc`

**Dates:** Unix ms — `createdAt>1704067200000`

## URL Encoding

**MCP clients:** Send raw values — server encodes automatically.

**Exception:** If VALUE contains operators (`: = , | > < ( )`), pre-encode them (e.g., `name:Hello%2CWorld`).

**Raw HTTP:** Double-encode operators in values (`%2C` → `%252C`).

## Folder Filtering

| Entity | Parent field | Path field (recursive) |
|--------|--------------|------------------------|
| Device | `deviceGroupId` | `deviceGroupPath` |
| Content/Playlist | `contentGroupId` | `contentGroupPath` |
| File | `fileFolderId` | `fileFolderPath` |
| DeviceGroup | `parentId` | `deviceGroupPath` |
| ContentGroup | `parentId` | `contentGroupPath` |
| FileFolder | `parentId` | `fileFolderPath` |

**Patterns:** Direct: `{field}={id}` — Recursive: `{path}:{id}` — Root: `parentId=NULL`

## Team Filtering

All entities use `teamAssignments` field:
```
teamAssignments.team.id={teamId}
teamAssignments.team.id=A|teamAssignments.team.id=B
teamAssignments=NULL
teamAssignments=!NULL
```

## Examples

| Goal | Query |
|------|-------|
| By ID | `id=abc123` |
| Name contains | `name:test` |
| Has content | `content=!NULL` |
| In set | `type∈SCREEN%2CTABLET` |
| Has tag | `tags:promo` |
| Tags AND | `tags:a,tags:b` |
| Folder recursive | `deviceGroupPath:uuid` |
