# Querying with TaxiQL

> Learn how to write queries using TaxiQL, Taxi's semantic query language

Source: https://taxilang.org/docs/taxiql/querying

## Introduction

TaxiQL is Taxi's query language for fetching and transforming data across your systems.
Rather than writing integration code against specific APIs or databases, TaxiQL lets you declare what data you want using semantic types.

When you write a TaxiQL query, you're describing the meaning of the data you need, not where to find it.
Query engines like [Orbital](https://orbitalhq.com) use these semantic types to automatically
discover and orchestrate the necessary service calls - whether that's a simple database query, or a complex flow across REST APIs,
message queues, and serverless functions.

This semantic approach means your queries remain stable even as your architecture evolves.
As services change their APIs or data moves between systems, TaxiQL adapts automatically,
eliminating the traditional maintenance burden of integration code.

## Basic syntax
The basic syntax of a TaxiQL query looks like this:

```taxi
// Find all the people
find { Person[] }

// Find a person named Jim
find { Person( FirstName == 'Jim' ) }

// Find all the people named Jim
find { Person[]( FirstName == 'Jim' ) }

// Find a stream of person events from somewhere
stream { PersonEvents }
```

Here's some interactive examples:

**Example: Fetch a list of data from a service**

Fetches a list of `Person[]` instances.

Schema:

```taxi
closed model Person {
    firstName : FirstName inherits String
    lastName : LastName inherits String
    age : Age inherits Int
}

service PersonApi {
    operation listPeople():Person[]
    operation findPerson( FirstName ):Person(...)
}
```

Query:

```taxi
// Find all the people
find { Person[] }
```

Result:

```json
[
  {
    "firstName": "Jim",
    "lastName": "Jackson",
    "age": 38
  },
  {
    "firstName": "Alice",
    "lastName": "Jackson",
    "age": 34
  },
  {
    "firstName": "Annie",
    "lastName": "Jackson",
    "age": 12
  }
]
```

**Example: Server-side filtering**

Adding constraints to the data being fetched controls the APIs that are called, only calling operations that satisfy the constraints

Schema:

```taxi
closed model Person {
    firstName : FirstName inherits String
    lastName : LastName inherits String
    age : Age inherits Int
}

service PersonApi {
    operation listPeople():Person[]
    operation findPerson( FirstName ):Person(...)
}
```

Query:

```taxi
// Call an operation that specifically returns a person
// looked up by their first name
// This query matches the operation defined as:
// operation findPerson( FirstName ):Person(...)
find { Person(FirstName == 'Jim') }
```

Result:

```json
{
  "firstName": "Jim",
  "lastName": "Jackson",
  "age": 38
}
```

Learn more about [server-side filtering and constraints](#server-side-constraints)

---

**Example: Client side filtering**

Fetches data by calling an operation, then filters the result

Schema:

```taxi
closed model Person {
    firstName : FirstName inherits String
    lastName : LastName inherits String
    age : Age inherits Int
}

service PersonApi {
    operation listPeople():Person[]
    operation findPerson( FirstName ):Person(...)
}
```

Query:

```taxi
// Fetch all the people from the server, then 
// filter those to people younger than 20
find { Person[].filter( (Age) -> Age < 20) }
```

Result:

```json
{
  "firstName": "Annie",
  "lastName": "Jackson",
  "age": 12
}
```

Learn more about [client-side filtering](#client-side-filtering) and [expressions](https://taxilang.org/docs/taxiql/expressions-traversal)

## Navigating data structures

You can access properties in models in two ways:

 - Using semantic types (preferred) - `Customer::AddressLine`
 - Using property names (classic, but not recommended) - `customer.addressLine`

You can also mix 'n' match these approaches.

> **Note: Use types to stay decoupled and prevent breaking changes**
>
> Using types to access data keeps systems loosely coupled.
>
> When consumers request data by its type, values are returned regardless of field names or nesting structure.\n\nAs schemas evolve, type-based access remains resilient to structural changes.

### Using types to traverse data

Use the `::` operator to perform deep traversal through data structures:

```taxi
// Find AddressLine1 at any depth within Customer
Customer::AddressLine1

// Chain deep traversals
// Find Address anywhere beneath Customer, and then PostCode anywhere beneath Address
Customer::Address::PostCode
```

The :: operator performs deep traversal, searching recursively through all nested levels of the data structure, not just immediate fields.

eg:

```taxi
// Finds an instance of AddressLine1 anywhere in a Customer model 
Customer::AddressLine1
```

> **Note: Ambiguous results return null**
>
> When writing an expression like `Customer::AddressLine1`, the result must select exactly one field, otherwise the statement is ambiguous, and the query engine returns `null`. See [Uniqueness](#uniqueness-and-type-traversal) for more information.

**Example: Using types to traverse child properties**

This example shows three different approaches to requesting a field by it's type, each with increasing specificity

Schema:

```taxi
model Customer {
    address : Address
}
model Address {
    houseNumber: HouseNumber inherits String
    line1: AddressLine1 inherits String
    postcode: PostCode inherits String
}
```

Query:

```taxi
import Address
import Customer
import AddressLine1
given { Customer = {
    address: {
        houseNumber: '5a',
        line1: 'Semantic Ave',
        postcode: 'SW2 4NN'
    }
}}
find { 
    // Request simply by type - requires the type be unamgibuous
    addressLine1: AddressLine1
    // Find AddressLine1 anywhere in a customer type
    customerAddressLine1: Customer::AddressLine1
    // Or be more specific, by describing the entire structure:
    mostSpecificAddressLine1: Customer::Address::AddressLine1
}
```

Result:

```json
{
  "addressLine1": "Semantic Ave",
  "customerAddressLine1": "Semantic Ave",
  "mostSpecificAddressLine1": "Semantic Ave"
}
```

### Uniqueness and type traversal
When requesting data using a type, ambiguity arises if there's more than one location of a type.

If the requested type is ambiguous within the data (ie., there are multiple instances found when a single instance was requested), then Taxi returns `null`.

To resolve this ambiguity, you have three options:

1. **Request all instances** using array syntax
2. **Use structural navigation** with property names
3. **Use structural navigation** with type access syntax

For example:

**Example: Multiple values return null**

In this query, `PersonName` is not unique, so returns null

Schema:

```taxi
model FlightBooking {
    passengers: Passenger[]
}
model Passenger {
    name : PersonName inherits String
}
```

Query:

```taxi
given {
    FlightBooking = {
        passengers: [
            { name: 'Jim'},
            { name: 'Jill'}
        ]
    }
}
find { 
    // This returns null, as PersonName is not unique
    passengerName: PersonName
}
```

Result:

```json
{
  "passengerName": null
}
```

Instead, you can either select all instances (eg: in the preceding example, by requesting `PersonName[]` instead of `PersonName`), or add selectors to make the
selected instance unambiguous:

**Example: Resolving type ambiguity**

Either select the array, or use qualifiers to make the selected instance unambiguous

Schema:

```taxi
model FlightBooking {
    passengers: Passenger[]
}
model Passenger {
    name : PersonName inherits String
}
```

Query:

```taxi
given {
    FlightBooking = {
        passengers: [
            { name: 'Jim'},
            { name: 'Jill'}
        ]
    }
}
find { 
    // Returns all PersonName instances as an array
    allPassengerNames: PersonName[]
    // Navigate to specific passenger by index, then access property
    firstPassengerName: Passenger[].getAtIndex(0).name
    // Navigate to specific passenger, then access by type
    secondPassengerName: Passenger[].getAtIndex(1)::PersonName
}
```

Result:

```json
{
  "allPassengerNames": [
    "Jim",
    "Jill"
  ],
  "firstPassengerName": "Jim",
  "secondPassengerName": "Jill"
}
```

### Using property names

You can reference data using property names, by using the dot-selector `.`. 

eg:
```taxi
model Customer {
   profile: Profile
}

model Profile {
   email: EmailAddress
}

// Property access - requires exact structure
customer.profile.email
```

**Example: Accessing properties by name**

Access properties by name using standard 'dot' syntax

Schema:

```taxi
model Customer {
    address : Address
}
model Address {
    houseNumber: HouseNumber inherits String
    line1: AddressLine1 inherits String
    postcode: PostCode inherits String
}
```

Query:

```taxi
given { 
    // Note that the customer variable is named here.
    customer: Customer = {
    address: {
        houseNumber: '5a',
        line1: 'Semantic Ave',
        postcode: 'SW2 4NN'
    }
}}
find { 
    // Accessing a property by chaining property access
    addressLine1: String = customer.address.line1
    
    // mixing property name access with type references is also possible
    postcode: customer.address::PostCode
}
```

Result:

```json
{
  "addressLine1": "Semantic Ave",
  "postcode": "SW2 4NN"
}
```

### Mixing approaches

Combine traversal with property access:

```taxi
// Type traversal then property
Customer::Address.streetName

// Property access then traversal
customer.addresses::PostCode
```

## Constraints and filtering

### Server-side constraints

Constraints limit which services are called based on their declared capabilities:

```taxi
// Only calls services that can filter by FirstName
find { Customer(FirstName == 'Jimmy') }

// Multiple constraints
find { Customer[](
   FirstName == 'Jimmy' && 
   LastName == 'Smith'
) }
```

Services declare their constraint support:

```taxi
service CustomerService {
   // Declares support for FirstName and LastName filtering
   operation findCustomersWithName(
      @PathVariable first : FirstName, 
      @PathVariable last : LastName
   ): Customer(FirstName == first && LastName == last) 
}
```

### Client-side filtering

For additional filtering after data retrieval:

```taxi
// Filter applied after data is fetched
find { Customer[].filter((FirstName) -> FirstName == 'Jimmy') }

// Multiple conditions
find { 
   Customer[].filter((Customer) -> 
      Customer::FirstName == 'Jimmy' && 
      Customer::Age > 21
   ) 
}
```

> **Note: Performance consideration**
>
> Server-side constraints are more efficient as they reduce data transfer. Client-side filtering is more flexible but may result in more data being transferred.

**Example: Client-side filtering (single condition)**

Fetches every customer, then keeps only those whose `FirstName` is `Jimmy`.

Schema:

```taxi
model Customer {
   firstName : FirstName inherits String
   lastName : LastName inherits String
   age : Age inherits Int
}
```

Query:

```taxi
given {
   customers : Customer[] = [
      { firstName: 'Jimmy', lastName: 'Smith', age: 30 },
      { firstName: 'Jimmy', lastName: 'Jones', age: 45 },
      { firstName: 'Jimmy', lastName: 'Fry', age: 18 },
      { firstName: 'Sarah', lastName: 'Smith', age: 40 }
   ]
}
find { Customer[].filter((FirstName) -> FirstName == 'Jimmy') }
```

Result:

```json
[
  {
    "firstName": "Jimmy",
    "lastName": "Smith",
    "age": 30
  },
  {
    "firstName": "Jimmy",
    "lastName": "Jones",
    "age": 45
  },
  {
    "firstName": "Jimmy",
    "lastName": "Fry",
    "age": 18
  }
]
```

**Example: Client-side filtering (multiple conditions)**

Combine conditions with `&&`. Naming the item `Customer` lets you read its fields by type - here only the Jimmys older than 21 remain.

Schema:

```taxi
model Customer {
   firstName : FirstName inherits String
   lastName : LastName inherits String
   age : Age inherits Int
}
```

Query:

```taxi
given {
   customers : Customer[] = [
      { firstName: 'Jimmy', lastName: 'Smith', age: 30 },
      { firstName: 'Jimmy', lastName: 'Jones', age: 45 },
      { firstName: 'Jimmy', lastName: 'Fry', age: 18 },
      { firstName: 'Sarah', lastName: 'Smith', age: 40 }
   ]
}
find { Customer[].filter((Customer) -> Customer::FirstName == 'Jimmy' && Customer::Age > 21) }
```

Result:

```json
[
  {
    "firstName": "Jimmy",
    "lastName": "Smith",
    "age": 30
  },
  {
    "firstName": "Jimmy",
    "lastName": "Jones",
    "age": 45
  }
]
```

## Collection options

Any collection expression can be shaped with **collection options** — named arguments that limit, order, paginate and de-duplicate results.
They're written after the array marker, alongside an optional predicate:

```taxi
find { Person[](CountryCode == 'GB', limit: 10, orderBy: DateOfBirth desc) }
```

**Example: Predicate, limit and order together**

Fetches people in `GB`, orders them by date of birth (newest first), then keeps the first two.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation findPeopleByCountry(CountryCode):Person[](https://taxilang.org/docs/taxiql/...)
}
```

Query:

```taxi
find { Person[](CountryCode == 'GB', limit: 2, orderBy: DateOfBirth desc) }
```

Result:

```json
[
  {
    "id": "4",
    "name": "Dave",
    "country": "GB",
    "dateOfBirth": "2001-07-30",
    "email": "dave@example.com"
  },
  {
    "id": "1",
    "name": "Alice",
    "country": "GB",
    "dateOfBirth": "1990-05-01",
    "email": "alice@example.com"
  }
]
```

Collection options describe *what* the result should look like, not *how* to compute it.
An option-carrying collection reads as: an optional single predicate first (`CountryCode == 'GB'`, which restricts which values match), followed by zero or more named options (`limit: 10`, `orderBy: DateOfBirth desc`, which shape the resulting collection).

### Syntax

```taxi
Type[]( predicate?, option: value, ... )
```

- The predicate, if present, must come first.
- Options are named (`name: value`) and unordered among themselves.
- Each option may appear at most once.

To combine multiple conditions, write a single boolean expression rather than several predicates:

```taxi
// Valid - one boolean predicate, then options
find { Person[](CountryCode == 'GB' && Status == 'Active', limit: 10) }
```

### Available options

| Option | Type | Meaning | Notes |
|--------|------|---------|-------|
| `limit` | `Int` | Return at most N items | "At most" — fewer may be returned if fewer are available. Must be non-negative. |
| `offset` | `Int` | Skip the first N items | Must be non-negative. Warns if used without `orderBy`. |
| `after` | `String` | Return the page after an opaque cursor | Mutually exclusive with `before`; can't be combined with `offset`. |
| `before` | `String` | Return the page before an opaque cursor | Mutually exclusive with `after`; can't be combined with `offset`. |
| `orderBy` | order term(s) | Order the collection by one or more expressions | `Type asc\|desc`; direction defaults to `asc`. Single term or list. |
| `uniqueBy` | expression(s) | Keep at most one item per unique key | Single expression or list. |

### Ordering

`orderBy` takes a single term, or a list of terms for multi-field ordering.
Each term is an expression (usually a type) with an optional direction — `asc` or `desc`, defaulting to `asc`:

```taxi
// Single field, explicit direction
find { Person[](orderBy: DateOfBirth desc) }

// Direction defaults to ascending
find { Person[](orderBy: PersonName) }

// Multiple fields - use a list to avoid comma ambiguity
find { Person[](orderBy: [DateOfBirth desc, PersonName asc]) }
```

**Example: Ordering by multiple fields**

Orders by date of birth descending, breaking ties by name ascending. Alice and Bob share a birth date, so name decides their order.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation listPeople():Person[]
}
```

Query:

```taxi
find { Person[](orderBy: [DateOfBirth desc, PersonName asc]) }
```

Result:

```json
[
  {
    "id": "1",
    "name": "Alice",
    "country": "GB",
    "dateOfBirth": "1990-05-01",
    "email": "alice@example.com"
  },
  {
    "id": "2",
    "name": "Bob",
    "country": "US",
    "dateOfBirth": "1990-05-01",
    "email": "bob@example.com"
  },
  {
    "id": "3",
    "name": "Carol",
    "country": "GB",
    "dateOfBirth": "1978-11-23",
    "email": "carol@example.com"
  }
]
```

Multiple order terms must be wrapped in a list.
Writing them as bare comma-separated terms isn't allowed, as the comma is ambiguous with the surrounding option list.

### Deduplicating with uniqueBy

`uniqueBy` keeps at most one item for each distinct key.
Pass a single expression, or a list to key on a combination:

```taxi
// One person per PersonId
find { Person[](uniqueBy: PersonId) }

// One person per (CountryCode, NationalInsuranceNumber) pair
find { Person[](uniqueBy: [CountryCode, NationalInsuranceNumber]) }
```

**Example: Deduplicating with uniqueBy**

Keeps one person per `(CountryCode, EmailAddress)` pair. Alice and Bob share both, so Bob is dropped; Carol's country differs, so she stays.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation listPeople():Person[]
}
```

Query:

```taxi
find { Person[](uniqueBy: [CountryCode, EmailAddress]) }
```

Result:

```json
[
  {
    "id": "1",
    "name": "Alice",
    "country": "GB",
    "dateOfBirth": "1990-05-01",
    "email": "shared@example.com"
  },
  {
    "id": "3",
    "name": "Carol",
    "country": "US",
    "dateOfBirth": "1978-11-23",
    "email": "shared@example.com"
  }
]
```

Because it keys on semantic value, `uniqueBy` isn't the same as a generic `.distinct()` — it de-duplicates by the meaning of the requested type(s).

### Cursor pagination

`after` and `before` request a page of results relative to an opaque cursor string.
They're mutually exclusive, and can't be combined with `offset`:

```taxi
query FindPeople(cursor: String, pageSize: Int) {
   find { Person[](after: cursor, limit: pageSize, orderBy: PersonName asc) }
}
```

Taxi treats the cursor value as opaque.
Whether cursors are honoured at runtime depends on the engine and the underlying source — support is currently limited, so check your engine's capabilities before relying on them.

### Parameterised values

Option values can be literals or any expression that's resolvable *before* the collection is fetched — most commonly a query parameter:

```taxi
query FindPeople(maxRows: Int) {
   find { Person[](limit: maxRows) }
}
```

**Example: Passing an option from a query parameter**

`limit` reads its value from the `maxRows` query parameter, which is resolved before the collection is fetched.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation listPeople():Person[]
}
```

Query:

```taxi
query FindPeople(maxRows: Int) {
   find { Person[](limit: maxRows) }
}
```

Result:

```json
[
  {
    "id": "1",
    "name": "Alice",
    "country": "GB",
    "dateOfBirth": "1990-05-01",
    "email": "alice@example.com"
  },
  {
    "id": "2",
    "name": "Bob",
    "country": "US",
    "dateOfBirth": "1985-03-12",
    "email": "bob@example.com"
  }
]
```

An option may reference query arguments and values from an enclosing scope, but not values produced by the collection it configures.
For example `Person[](limit: PersonAge)` is invalid, because `PersonAge` belongs to the very `Person` values being fetched.

### Logical order

However you write them, options are applied in a fixed logical order:

```
predicate → uniqueBy → orderBy → offset → limit
```

This matters for correctness. For example:

```taxi
find { Person[](uniqueBy: EmailAddress, limit: 10) }
```

**Example: Options apply in a fixed logical order**

`uniqueBy` runs before `limit`, so this returns up to 10 unique people rather than 10 rows that are then deduplicated down to fewer.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation listPeople():Person[]
}
```

Query:

```taxi
find { Person[](uniqueBy: EmailAddress, limit: 10) }
```

Result:

```json
[
  {
    "id": "1",
    "name": "Alice",
    "country": "GB",
    "dateOfBirth": "1990-05-01",
    "email": "team@example.com"
  },
  {
    "id": "3",
    "name": "Carol",
    "country": "GB",
    "dateOfBirth": "1978-11-23",
    "email": "carol@example.com"
  }
]
```

returns up to 10 *unique* people — deduplication happens first, then the limit — rather than 10 people that are then deduplicated down to fewer.
Likewise `orderBy` always precedes `limit`, so `orderBy: DateOfBirth desc, limit: 10` means "order everything, then take 10", not "take 10 arbitrary rows, then sort them".

### Where options apply — source vs projection

Options attach to the specific collection they follow, and their **position determines which phase they shape**.
On a `find`, they shape the source fetch. On a projection's array marker, they shape the projected result:

```taxi
// Fetch up to 100 people, project them, then return up to 10 projected results
find { Person[](limit: 100) } as {
   name: PersonName
}[](limit: 10)
```

**Example: Source limit vs projection limit**

The source `Person[]` is fetched with `limit: 100`, but the projection returns only the first two results.

Schema:

```taxi
model Person {
   id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

service PersonApi {
   operation listPeople():Person[]
}
```

Query:

```taxi
find { Person[](limit: 100) } as {
   name : PersonName
}[](limit: 2)
```

Result:

```json
[
  {
    "name": "Alice"
  },
  {
    "name": "Bob"
  }
]
```

> **Note: Position determines the phase**
>
> The two are **not** interchangeable. An option on the source `Person[]` limits what's fetched; an option on the projection's `[]` limits what's returned after projecting. Limiting the source to 100 and the projection to 10 fetches up to 100 rows and returns 10 — limiting the source to 10 would only ever fetch 10.

Options also work on nested collection fields inside a projection:

```taxi
find { Person[](CountryCode == 'GB', limit: 10, orderBy: PersonName asc) } as {
   name: PersonName
   transactions: Transaction[](limit: 20, orderBy: TransactionDate desc)
}[]
```

**Example: Options on a nested collection**

Each person's transactions are independently limited to their two most recent.

Schema:

```taxi
model Person {
   @Id id : PersonId inherits String
   name : PersonName inherits String
   country : CountryCode inherits String
   dateOfBirth : DateOfBirth inherits Date
   email : EmailAddress inherits String
}

model Transaction {
   personId : PersonId
   date : TransactionDate inherits Date
   amount : Amount inherits Decimal
}

service PersonApi {
   operation findPeopleByCountry(CountryCode):Person[](https://taxilang.org/docs/taxiql/...)
   operation getTransactions(PersonId):Transaction[]
}
```

Query:

```taxi
find { Person[](CountryCode == 'GB', limit: 10, orderBy: PersonName asc) } as {
   name : PersonName
   transactions : Transaction[](limit: 2, orderBy: TransactionDate desc)
}[]
```

Result:

```json
[
  {
    "name": "Alice",
    "transactions": [
      {
        "personId": "1",
        "date": "2023-06-01",
        "amount": 20
      },
      {
        "personId": "1",
        "date": "2023-03-01",
        "amount": 15
      }
    ]
  },
  {
    "name": "Dave",
    "transactions": [
      {
        "personId": "4",
        "date": "2024-01-01",
        "amount": 200
      },
      {
        "personId": "4",
        "date": "2022-05-01",
        "amount": 100
      }
    ]
  }
]
```

### Compile-time rules

Options are checked when your query compiles. The compiler rejects:

- Options on a non-collection type (`Person(limit: 10)` — `limit` needs `Person[]`).
- Unknown option names, and any option specified more than once.
- Values of the wrong type (`limit`/`offset` must be `Int`; `after`/`before` must be `String`).
- Statically-negative `limit` or `offset` (dynamic values are validated at runtime instead).
- Sort directions other than `asc` / `desc`.
- `after` and `before` together, or either cursor combined with `offset`.
- A predicate written after named options.

> **Warning: offset without orderBy**
>
> Using `offset` without an `orderBy` compiles with a warning: without a stable order, the result window may shift between calls, so successive pages aren't guaranteed to line up.

### Runtime behaviour

Collection options define the *meaning* of a result; the runtime semantics are engine-dependent.
A query engine such as [Orbital](https://orbitalhq.com) decides whether to push each option down to the data source, apply it within the engine, or reject combinations it can't satisfy safely.
For instance, `limit` on a stream simply takes the first N items and completes, whereas a global `orderBy` or `uniqueBy` over an unbounded stream is generally rejected.

## Given statements

Given statements make data available to your query without constraining which operations are called:

```taxi
// Basic given statement
given { EmailAddress = 'jimmy@demo.com' }
find { Customer }

// With variable name
given { email : EmailAddress = 'jimmy@demo.com' }
find { Customer }

// Multiple values
given {
   status : OrderStatus = 'PENDING'
   customerId : CustomerId = '123'
}
find { Order[] }
```

**Example: Feeding an input with given**

The `EmailAddress` provided in `given` is used to call an operation that looks up the matching customer.

Schema:

```taxi
model Customer {
   email : EmailAddress inherits String
   firstName : FirstName inherits String
   lastName : LastName inherits String
}

service CustomerApi {
   operation findCustomer(EmailAddress):Customer
}
```

Query:

```taxi
given { EmailAddress = 'jimmy@demo.com' }
find { Customer }
```

Result:

```json
{
  "email": "jimmy@demo.com",
  "firstName": "Jimmy",
  "lastName": "Smith"
}
```

### Given vs constraints

Understanding the difference is crucial:

```taxi
// Given: makes data available but doesn't restrict operations
given { status : OrderStatus = 'PENDING' }
find { Order[] }  // May return orders of any status

// Constraint: restricts which operations can be called
given { status : OrderStatus = 'PENDING' }
find { Order[](OrderStatus == status) }  // Only returns pending orders
```

> **Warning: Given doesn't constrain data**
>
> Providing data in `given` makes data available that can be used with API calls -- but it doesn't limit which operations can be called. To filter results or specify exact data requirements, use constraints in your `find` statement.

## Basic projections

Projections transform and enrich data:

```taxi
// Project to a different structure
find { Movie[] } as {
   title : MovieTitle
   director : DirectorName
   rating : RottenTomatoesScore
}[]

// Project to a named type
find { Book[] } as BookAndAuthor[]

// Select specific fields
find { Order[] } as {
   id        // Field shorthand
   status    
   total     
}[]
```

**Example: Projecting to a new structure**

Reshapes each `Movie` into a result containing only the fields you ask for.

Schema:

```taxi
model Movie {
   title : MovieTitle inherits String
   director : DirectorName inherits String
   rating : RottenTomatoesScore inherits Int
}
```

Query:

```taxi
given {
   movies : Movie[] = [
      { title: 'Jaws', director: 'Spielberg', rating: 97 },
      { title: 'Alien', director: 'Scott', rating: 93 }
   ]
}
find { Movie[] } as {
   title : MovieTitle
   director : DirectorName
   rating : RottenTomatoesScore
}[]
```

Result:

```json
[
  {
    "title": "Jaws",
    "director": "Spielberg",
    "rating": 97
  },
  {
    "title": "Alien",
    "director": "Scott",
    "rating": 93
  }
]
```

## Named queries

Save and reuse queries:

```taxi
// Simple named query
query PendingOrders {
   find { Order[](Status == 'PENDING') }
}

// Parameterized query
query FindOrdersByStatus(status: OrderStatus) {
   find { Order[](Status == status) }
}
```

## Including and excluding services

Control which services are called:

```taxi
// Only use specific services
find { Film[] }
using { 
   FilmService::getFilms,    // Specific operation
   ReviewService             // Entire service
}

// Exclude specific services
find { Film[] }
excluding { 
   ImdbApi,                      // Exclude entire service
   RottenTomatoes::getReviews    // Exclude specific operation
}
```

## Supported operators

| Symbol | Meaning                  |
|--------|--------------------------|
| `==`   | Equal to                 |
| `!=`   | Not equal to             |
| `>`    | Greater than             |
| `>=`   | Greater than or equal to |
| `<`    | Less than                |
| `<=`   | Less than or equal to    |

## Next steps

- Learn about [projections](https://taxilang.org/docs/taxiql/projections) for transforming data
- Explore [expressions and type traversal](https://taxilang.org/docs/taxiql/expressions-traversal) for navigating data
- Understand [functions](https://taxilang.org/docs/taxiql/functions) for data manipulation
