# JSON Query Syntax Reference

The Resource Registry API provides a powerful JSON-based Query-By-Example (QBE) engine. This syntax allows clients to perform deep topological matching, graph traversal, and response projection without writing raw backend SQL.

The engine is universally accessible across all collections via their respective execution endpoints and read-only mirrors:
- **Contexts:** `QUERY /contexts` and `QUERY /access/contexts`
- **Schema Types:** `QUERY /types` and `QUERY /access/types`
- **Instances:** `QUERY /instances` and `QUERY /access/instances`
- **Query Templates:** `QUERY /query-templates` and `QUERY /access/query-templates`

This document serves as the formal reference manual for constructing these JSON queries.

## Core Principles

- **Query-By-Example:** The structure of the JSON query mimics the structure of the desired output. You nest objects (e.g., `consistsOf`, `isRelatedTo`) exactly as they appear in a standard representation.
- **Mandatory Root `type`:** Every JSON query must explicitly declare the `"type"` keyword at its root. This instructs the query engine on which collection/class to scan.
  - When querying Contexts, the type is structurally fixed (e.g., `"type": "Context"`).
  - When querying Schema Types, the type is one of the queryable Type-definition categories: `"type": "ResourceSchema"`, `"FacetSchema"`, `"IsRelatedToSchema"`, `"ConsistsOfSchema"`, or `"PropertySchema"` (not the concrete schema name being defined — e.g. to find the `EService` schema definition, query `{"type": "ResourceSchema", "name": "EService"}`, not `{"type": "EService"}`).
  - When querying Instances, the type is the specific schema name (e.g., `"type": "EService"` or `"type": "HostingNode"`).
  - When querying Templates, the type is `"type": "QueryTemplate"`.
- **Mandatory Nested `type`:** Because the engine matches the query tree exactly against the database schema, the `"type"` keyword is currently **strictly mandatory at every nested structural level** of the query (e.g., inside `consistsOf` and `isRelatedTo` arrays, and within their `source` or `target` objects). Omitting the `type` inside a nested relationship causes the query evaluation to fail. *(This is unrelated to `depth`-based `name` matching within Type-definition queries — see below — which is a distinct mechanism scoped to the Types collection only.)*
- **System Keywords (`_` Prefix):** To avoid namespace collisions with user-defined property fields (Open-World paradigm), all query operators, traversal commands, and projection directives strictly begin with an underscore `_` (e.g., `_emit`, `_and`, `_eq`, `_source`).

## Depth-Based Type-Name Matching (Type-Definition Queries Only)

This section applies exclusively to `QUERY /types` (and its `/access` mirror), i.e. queries whose root `"type"` is one of the Type-definition categories (`ResourceSchema`, `FacetSchema`, `IsRelatedToSchema`, `ConsistsOfSchema`, `PropertySchema`). It does not apply to Instance, Context, or Query Template queries, where `polymorphic` instead controls entry-point subclass expansion (see [`polymorphic`](../11-api-query-parameters.html#polymorphic)); the Types collection deliberately reuses `depth` instead, for consistency with `GET /types/{type-name}` within the same collection (see [`depth`](../11-api-query-parameters.html#depth)).

Type-definition records carry no OrientDB subclass relationship to each other — every registered schema in a category is a flat row in the same shared class, distinguished only by its own `name` and its declared `extendedTypes`. Consequently, an equality match against a Type name — the query's own `name` field, or the `source`/`target` name inside a Relation Type definition — is resolved against the **descendant closure of that name up to `depth` levels** (the same Type-hierarchy walk that powers `GET /types/{type-name}?depth=N`), not just the literal name. `depth=0` restricts the match to the exact name only; a positive `depth=N` includes descendants up to N levels down; `depth=-1` includes all descendants recursively. On `QUERY /types`, `depth` defaults to `-1` (polymorphic-equivalent matching, consistent with the `polymorphic=true` default on every other `QUERY` endpoint in this API — see the [`depth`](../11-api-query-parameters.html#depth) section for the full default-value rationale).

*Example — `depth`-based `source`/`target` matching on a Relation Type:*
```json
{
  "type": "IsRelatedToSchema",
  "source": "Service",
  "target": "Service"
}
```
With the default `depth=-1` (or an explicit `?depth=-1`), this matches `Uses` even though `Uses` is declared between two `EService` instances (a subtype of `Service`), because `EService` is a descendant of `Service`. With an explicit `?depth=0`, only Relation Type definitions declared with the *literal* source and target name `Service` are matched.

*Example — `depth`-based `name` matching combined with a structural constraint:*
```json
{
  "type": "FacetSchema",
  "name": "Facet",
  "properties": { /* a constraint matching an element with property name "name" */ }
}
```
`Facet` is the abstract base schema and declares no `properties` of its own. With the default `depth=-1` (or an explicit `?depth=-1`), the `name` match expands to `Facet` and every registered descendant (e.g. `ContactFacet`), and any other constraint in the same query — such as this `properties` check — is then evaluated independently against each candidate in that expanded set, using the array-matching operators already described under [Comparison Operators](#comparison-operators). Result: every Facet Type definition (base or descendant) that declares a property named `name`, e.g. `ContactFacet`. With an explicit `?depth=0`, this query returns an **empty array** instead (the literal `Facet` record does not have a property named `name`).

## Response Projection (`_emit`)

By default, a query returns the full representation of the matched instances. To optimize payloads and return only specific fields (GraphQL style), use the `_emit` block.

The `_emit` object acts as an alias map: the keys are the actual fields in the database, and the values are the custom aliases you want returned in the final JSON response.

### Canonical `_emit` Precedence Rules
This subsection is **normative** and serves as the single source of truth for `_emit` behavior across direct JSON queries and stored query-template execution:
1. **Projection-first behavior:** When `_emit` is present, the response is strictly projection-based (selected fields/aliases) and will not return the full instance representation.
2. **Metadata/context expansion disabled:** Structural inclusion parameters such as `metadataPolicy` and `includeContexts` are completely ignored and have no effect when `_emit` is used.
3. **Privacy masking still enforced:** If `_emit` projects sensitive fields like `metadata.createdBy` or `metadata.lastUpdateBy` (under any alias), their values are strictly masked as `"[REDACTED]"` for unprivileged roles (`IS-Admin / Ctx-M`, `IS-Editor`, `IS-Contrib`, `IS-Member`).
4. **Alias-agnostic enforcement:** Privacy masking is applied based on the true source of the projected field, regardless of the custom alias name chosen by the client.
5. **Scope of application:** These rules apply uniformly to all query engines (`QUERY /{collection}`, `QUERY /query-templates/{name}`, their respective `/access` mirrors, and the corresponding `POST .../query` compatibility fallbacks).

### Projecting Context Fields
```json
{
  "type": "Context",
  "effectiveState": "suspended",
  "_emit": {
    "id": "ContextUUID",
    "name": "ContextName",
    "path" : "ContextPath"
  }
}
```

### Projecting Type Schema Fields
```json
{
  "type": "ResourceSchema",
  "_emit": {
    "id": "UUID",
    "name": "Name",
    "description": "Description"
  }
}
```

### Projecting Instance Fields
```json
{
  "type": "EService",
  "_emit": {
    "id": "ServiceID",
    "type": "ElementType"
  }
}
```

### Deep Projection
You can place `_emit` blocks at any nested level of the query to project fields from related elements into a flat response object.

```json
{
  "type": "EService",
  "consistsOf": [
    {
      "type": "IsIdentifiedBy",
      "target": {
        "type": "SoftwareFacet",
        "_emit": {
          "name": "SoftwareName",
          "version": "SoftwareVersion"
        }
      }
    }
  ]
}
```

### Projecting QueryTemplate Fields
```json
{
  "type": "EService",
  "_emit": {
    "id": "ServiceID",
    "type": "ElementType"
  }
}
```

## Logical Operators

The engine supports explicitly grouping constraints using `_and`, `_or`, and `_not`.

### `_or` (Match ANY condition)
Use `_or` when you need to match across completely different topological branches or distinct fields (for simple multi-value matching on the same field, prefer the `_in` operator).
```json
{
  "type": "EService",
  "_or": [
    {
      "consistsOf": [
        { 
          "type": "ConsistsOf",
          "target": { "type": "StateFacet", "value": "down" } 
        }
      ]
    },
    {
      "isRelatedTo": [
        { 
          "type": "Activates", 
          "source": { "type": "HostingNode" } 
        }
      ]
    }
  ]
}
```

### `_and` (Match ALL conditions)
Normally, providing multiple fields in a JSON object acts as an implicit AND. However, you can use `_and` for complex groupings.
```json
{
  "type": "EService",
  "_and": {
    "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
    "metadata": { "createdBy": "luca.frosini" }
  }
}
```

## Comparison Operators

By default, providing a scalar value (e.g., `"name": "gcat"`) implies an Exact Match (`_eq`). For advanced filtering, you can supply an operator object.

| Operator | Description |
|----------|-------------|
| `_eq` | Exact match (default behavior). |
| `_ne` | Not equal to the specified value. |
| `_gt` | Greater than. |
| `_gte` | Greater than or equal to. |
| `_lt` | Less than. |
| `_lte` | Less than or equal to. |
| `_between` | Value is between two boundaries. |
| `_is` | Evaluates nullity (e.g., `_is: "NULL"`). |
| `_like` | Simple wildcard matching (e.g., `Luk%`). |
| `_containsText` | Full-text search match. |
| `_matches` | Regular expression match. |
| `_isDefined` | Evaluates if the field exists (true/false). |
| `_isNotDefined` | Evaluates if the field does not exist. |

### Collection / Map Operators
For property fields that contain collections or maps:
| Operator | Description |
|----------|-------------|
| `_in` | Matches any value in the provided array. |
| `_contains` | The target array contains the specified element. |
| `_containsAll` | The target array contains all the specified elements. |
| `_containsAny` | The target array contains at least one of the specified elements. |
| `_containsKey` | The target map contains the specified key. |
| `_containsValue` | The target map contains the specified value. |

### Example: Not Equal (`_ne`)
```json
{
  "type": "StateFacet",
  "metadata": {
    "createdBy": { "_ne": "luca.frosini" }
  }
}
```

## Inverse Traversal (`_source`)

Standard queries traverse the graph forwards (e.g., from `Resource` to its `ConsistsOf` relations to the `Facet`). 
However, if your query's entry point (root node) is a `Facet` or `Relation`, you can traverse *backwards* to find the `Resource` that owns it by using the `_source` keyword.

*(Note: `_source` is strictly permitted only on the entry-point object of the JSON query. You cannot use it deep within a forward-traversal chain).*

### Example: Finding the EService that owns a specific StateFacet
```json
{
  "type": "StateFacet",
  "value": "down",
  "_source": {
    "type": "ConsistsOf",
    "source": {
      "type": "EService",
      "_emit": {
        "id": "ServiceUUID"
      }
    }
  }
}
```

## Query Templates (Variables)

```eval_rst
When the JSON query is saved as a ``QueryTemplate`` (via ``PUT /query-templates/{name}``), you can insert template variables prefixed with \$. These variables are dynamically substituted at execution time based on the JSON body provided to ``QUERY /query-templates/{name}``.
```

**Target Limitation:** The Query Template mechanism is structurally restricted to **Instances**. Therefore, the root `"type"` in a saved template must always specify a valid **Entity** or **Relation** type (e.g., **Resource**, **EService**). Query templates cannot be used to save queries targeting Contexts, Type Schemas, or other Query Templates.

```json
{
  "type": "EService",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "target": {
        "type": "StateFacet",
        "value": "$targetState"
      }
    }
  ]
}
```
*(Execution Payload)*
```json
{
  "$targetState": "ready"
}
