# Query overview

Understanding the dcupl query structure, groups, and conditions for building complex queries.

## QueryExecutionContext

A query is a tree of conditions and groups of conditions. You run one directly with `dcupl.query.execute(context)`, pass one as the seed `query` of `dcupl.lists.create()`, or build one step by step on a list with `list.catalog.query` (`list.catalog.query.current()` returns it). In every case the query is a `QueryExecutionContext`.

`QueryExecutionContext` is the root container for executing queries. It holds:

- **modelKey** - Target model to query
- **queries** - Array of conditions and groups (the query tree)
- **groupType** - How to combine queries: `'and'` or `'or'`
- **Item options** - Pagination, projection, sorting

Think of it as the **complete specification** for a query operation.

```typescript filename="query-context.ts"
import type { QueryExecutionContext } from '@dcupl/common';

// The item type used by the examples on this page
type Product = { key: string; name: string; status: string; category: string; price: number };

// Basic structure
const context: QueryExecutionContext<Product> = {
  modelKey: 'Product',
  groupType: 'and', // Combine with AND (default)
  queries: [
    // QueryCondition or QueryConditionGroup items
  ],
  // Optional: pagination, projection, sort
  start: 0,
  count: 50,
};
```

## QueryCondition

A `QueryCondition` is a single filter criterion:

- **attribute** - Property to filter on
- **operator** - How to compare (`eq`, `find`, `gte`, etc.)
- **value** - Value to compare against
- **options** - Optional: `transform`, `invert`, `arrayValueHandling`, `regexFlags` (for `find`)

See [Operators Reference](/sdk/api/query-operators) for all available operators.

```typescript filename="query-condition.ts"
import type { QueryCondition } from '@dcupl/common';

// Exact match
const exact: QueryCondition = {
  attribute: 'status',
  operator: 'eq',
  value: 'active',
};

// Text search: '/term/' matches anywhere in the value,
// a bare string must match the whole value
const search: QueryCondition = {
  attribute: 'name',
  operator: 'find',
  value: '/laptop/',
  options: { regexFlags: 'i' }, // case-insensitive
};

// Numeric range
const minPrice: QueryCondition = {
  attribute: 'price',
  operator: 'gte',
  value: 100,
};

// With options
const caseInsensitive: QueryCondition = {
  attribute: 'category',
  operator: 'eq',
  value: 'electronics',
  options: { transform: ['lowercase'] }, // compare lowercased values
};
```

## QueryConditionGroup

A `QueryConditionGroup` combines multiple conditions with boolean logic:

- **groupType** - `'and'` or `'or'`
- **queries** - Array of conditions (or nested groups)

#### AND group

All conditions must match. Use for narrowing results.

#### OR group

Any condition can match. Use for broadening results.

```typescript filename="query-groups.ts"
import type { QueryConditionGroup } from '@dcupl/common';

// AND group: active AND in stock
const andGroup: QueryConditionGroup = {
  groupType: 'and',
  queries: [
    { attribute: 'status', operator: 'eq', value: 'active' },
    { attribute: 'inStock', operator: 'eq', value: true },
  ],
};

// OR group: category is Electronics OR Computers
const orGroup: QueryConditionGroup = {
  groupType: 'or',
  queries: [
    { attribute: 'category', operator: 'eq', value: 'Electronics' },
    { attribute: 'category', operator: 'eq', value: 'Computers' },
  ],
};
```

## Nesting groups

Groups can contain other groups, enabling complex boolean expressions:

`(A AND B) OR (C AND D)`

The recursive structure allows arbitrary nesting depth for any boolean logic you need.

Structure:

```text
QueryExecutionContext
└── queries[]
    ├── QueryCondition
    └── QueryConditionGroup
        └── queries[]
            ├── QueryCondition
            └── QueryConditionGroup (recursive)
```

```typescript filename="nested-groups.ts"
// Complex: (premium AND active) OR (featured AND sale)
// (Product: see the first example)
const context: QueryExecutionContext<Product> = {
  modelKey: 'Product',
  groupType: 'or', // Top level OR
  queries: [
    // First group: premium AND active
    {
      groupType: 'and',
      queries: [
        { attribute: 'tier', operator: 'eq', value: 'premium' },
        { attribute: 'status', operator: 'eq', value: 'active' },
      ],
    },
    // Second group: featured AND sale
    {
      groupType: 'and',
      queries: [
        { attribute: 'featured', operator: 'eq', value: true },
        { attribute: 'onSale', operator: 'eq', value: true },
      ],
    },
  ],
};
```

## Item options

`QueryExecutionContext` includes the item options (`ItemDataOptions` in `@dcupl/common`):

| Option | Description |
| --- | --- |
| `start` | Skip N items (pagination offset) |
| `count` | Limit results to N items |
| `projection` | Select specific properties to return, e.g. `{ $: false, name: true }` |
| `sort` | Order results: `{ attributes, order }` with `'ASC'` or `'DESC'` per attribute |

```typescript filename="item-options.ts"
// (Product: see the first example)
const context: QueryExecutionContext<Product> = {
  modelKey: 'Product',
  queries: [
    { attribute: 'status', operator: 'eq', value: 'active' },
  ],

  // Pagination: page 2, 20 items per page
  start: 20,
  count: 20,

  // Only return these fields (key is always included)
  projection: { $: false, name: true, price: true },

  // Sort by price descending
  sort: { attributes: ['price'], order: ['DESC'] },
};
```

## Building queries

Real-world example: E-commerce product filter

Requirements:

- Active products only
- Category: Electronics OR Computers
- Price between $100 and $1000
- Sort by price, paginated

The context object fully describes this complex filter in a declarative, serializable format. Conditions placed directly in an `'and'` group must all match, so the two price conditions form a range.

```typescript filename="building-queries.ts"
// dcupl: an initialized instance with a Product model (Product: see the first example)
const productFilter: QueryExecutionContext<Product> = {
  modelKey: 'Product',
  groupType: 'and',
  queries: [
    // Must be active
    { attribute: 'status', operator: 'eq', value: 'active' },

    // Category: Electronics OR Computers
    {
      groupType: 'or',
      queries: [
        { attribute: 'category', operator: 'eq', value: 'Electronics' },
        { attribute: 'category', operator: 'eq', value: 'Computers' },
      ],
    },

    // Price range: $100 - $1000
    { attribute: 'price', operator: 'gte', value: 100 },
    { attribute: 'price', operator: 'lte', value: 1000 },
  ],

  // Sort and paginate
  sort: { attributes: ['price'], order: ['ASC'] },
  start: 0,
  count: 24,
};

// Execute against the model
const results = dcupl.query.execute(productFilter);
```

## In action

On a list you rarely write the context yourself: `list.catalog.query` keeps the tree, and `addCondition`, `addGroup`, `setCondition` and `setGroup` change it. [Methods & Operators](/sdk/queries/methods-operators) explains each method; the [Catalog API](/sdk/api/catalog#query-operations) lists them all.

```typescript filename="in-action.ts"
const list = dcupl.lists.create({ modelKey: 'Product' });
list.catalog.query.addCondition({ attribute: 'status', operator: 'eq', value: 'active' });

// The list keeps its query as a QueryExecutionContext
const context = list.catalog.query.current();
// { modelKey: 'Product', groupKey: 'root', groupType: 'and',
//   queries: [{ groupKey: 'status', groupType: 'or', queries: [{ attribute: 'status', ... }] }] }

// Run the same context without a list
const sameItems = dcupl.query.execute(context);
```

## Next steps

- [Methods & Operators](/sdk/queries/methods-operators) — Build and change a list query step by step
- [Operators](/sdk/api/query-operators) — Complete reference of all filter operators
- [Deep queries](/sdk/queries/query-deep) — Query across model relationships
- [Lists](/sdk/core-concepts/lists) — Create filtered views of your models
