# BoundQuery

Parameterised query class for safe query composition.

The `BoundQuery` class represents a parameterised SurrealQL query with bound variables, providing safe query composition and preventing SQL injection.

**Import:**
```ts
import { BoundQuery } from 'surrealdb';
```

**Source:** [utils/bound-query.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/utils/bound-query.ts)

## Type parameters

- `R extends unknown[]` - Array of result types for the query

## Constructor

### `new BoundQuery(query?, bindings?)` {#constructor}

Create a new bound query.

```ts title="Syntax"
new BoundQuery() // Empty query
new BoundQuery(boundQuery) // Clone existing
new BoundQuery(query, bindings?) // From string and bindings
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>query</code> <label label="optional" /></td>
            <td><code>string | BoundQuery</code></td>
            <td>Query string or existing BoundQuery to clone.</td>
        </tr>
        <tr>
            <td><code>bindings</code> <label label="optional" /></td>
            <td><code>Record&lt;string, unknown&gt;</code></td>
            <td>Parameter bindings.</td>
        </tr>
    </tbody>
</table>

#### Examples

```ts
// Empty query
const query = new BoundQuery();

// From string
const query = new BoundQuery('SELECT * FROM users');

// With bindings
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
);

// Clone existing
const clone = new BoundQuery(existingQuery);
```

## Properties

### `query` {#query}

The query string with parameter placeholders.

**Type:** `string`

```ts
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
);

console.log(query.query);
// 'SELECT * FROM users WHERE age > $age'
```

---

### `bindings` {#bindings}

A copy of the parameter bindings.

**Type:** `Record<string, unknown>`

```ts
console.log(query.bindings);
// { age: 18 }
```

## Methods

### `.append()` {#append}

Append another query or string to this query.

```ts title="Method Syntax"
query.append(other)
query.append(queryString, bindings?)
query.append`template ${value}`
```

#### Parameters
<table>
    <thead>
        <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>other</code></td>
            <td><code>BoundQuery | string | TemplateStringsArray</code></td>
            <td>Query to append.</td>
        </tr>
        <tr>
            <td><code>bindings</code> <label label="optional" /></td>
            <td><code>Record&lt;string, unknown&gt;</code></td>
            <td>Bindings for the appended query.</td>
        </tr>
    </tbody>
</table>

#### Returns
`this` - Chainable

#### Examples

```ts title="Append BoundQuery"
const base = new BoundQuery('SELECT * FROM users WHERE 1=1');
const filter = new BoundQuery(' AND age > $age', { age: 18 });

base.append(filter);
```

```ts title="Append String"
const query = new BoundQuery('SELECT * FROM users');
query.append(' WHERE active = $active', { active: true });
```

```ts title="Append with Template"
const query = new BoundQuery('SELECT * FROM users');
const status = 'active';
query.append` WHERE status = ${status}`;
```

---

### `.toString()` {#tostring}

Get the query string.

```ts title="Method Syntax"
query.toString()
```

#### Returns
`string` - The query string

## Complete examples

### Basic parameterised query

```ts
import { BoundQuery } from 'surrealdb';

const query = new BoundQuery(
    'SELECT * FROM users WHERE age >= $minAge AND status = $status',
    {
        minAge: 18,
        status: 'active'
    }
);

const [users] = await db.query(query).collect();
```

### Building queries incrementally

```ts
// Start with base query
const query = new BoundQuery('SELECT * FROM products WHERE 1=1');

// Add conditions dynamically
if (category) {
    query.append(' AND category = $category', { category });
}

if (minPrice) {
    query.append(' AND price >= $minPrice', { minPrice });
}

if (maxPrice) {
    query.append(' AND price <= $maxPrice', { maxPrice });
}

query.append(' ORDER BY created_at DESC LIMIT $limit', { limit: 10 });

const [products] = await db.query(query).collect();
```

### Query builder pattern

```ts
class QueryBuilder {
    private query: BoundQuery;
    
    constructor(table: string) {
        this.query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);
    }
    
    where(field: string, value: unknown): this {
                this.query.append(` AND ${field} = $${field}`,
            { [field]: value });
        return this;
    }
    
    limit(count: number): this {
        this.query.append(' LIMIT $limit', { limit: count });
        return this;
    }
    
    build(): BoundQuery {
        return this.query;
    }
}

// Usage
const builder = new QueryBuilder('users');
const query = builder
    .where('status', 'active')
    .where('verified', true)
    .limit(10)
    .build();

const [users] = await db.query(query).collect();
```

### Reusable query fragments

```ts
// Define reusable fragments
const activeFilter = new BoundQuery('status = $status',
    { status: 'active' });
const verifiedFilter = new BoundQuery('verified = $verified',
    { verified: true });

// Combine them
const query = new BoundQuery('SELECT * FROM users WHERE ');
query.append(activeFilter);
query.append(' AND ');
query.append(verifiedFilter);

const [users] = await db.query(query).collect();
```

### Complex multi-statement query

```ts
const userId = new RecordId('users', 'john');
const postData = {
    title: 'My Post',
    content: 'Content here'
};

const query = new BoundQuery();

query.append('BEGIN TRANSACTION;');

query.append(
    'UPDATE $userId SET post_count += 1;',
    { userId }
);

query.append(
        'CREATE posts SET author = $author, title = $title,
        content = $content;',
    {
        author: userId,
        title: postData.title,
        content: postData.content
    }
);

query.append('COMMIT TRANSACTION;');

await db.query(query).collect();
```

### Pagination helper

```ts
function paginatedQuery(
    table: string,
    page: number,
    pageSize: number,
    filters?: Record<string, unknown>
): BoundQuery {
    const query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);
    
    if (filters) {
        for (const [key, value] of Object.entries(filters)) {
            query.append(` AND ${key} = $${key}`, { [key]: value });
        }
    }
    
    const offset = (page - 1) * pageSize;
    query.append(' START $offset LIMIT $limit', {
        offset,
        limit: pageSize
    });
    
    return query;
}

// Usage
const query = paginatedQuery('users', 2, 20, { status: 'active' });
const [users] = await db.query(query).collect();
```

## Best practices

### 1. Use surql template instead

For most cases, the `surql` template is easier:

```ts
// Good: surql template (recommended)
const query = surql`SELECT * FROM users WHERE age > ${age}`;

// Also good: BoundQuery (more manual)
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age',
    { age }
);
```

### 2. Validate parameter names

```ts
// Good: Consistent parameter naming
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $age AND status = $status',
    { age: 18, status: 'active' }
);

// Avoid: Mismatched names
const query = new BoundQuery(
    'SELECT * FROM users WHERE age > $minAge',
    { age: 18 } // Wrong key name
);
```

### 3. Use append() for dynamic queries

```ts
// Good: Incremental building
const query = new BoundQuery('SELECT * FROM users WHERE 1=1');
if (filter) {
    query.append(' AND status = $status', { status: filter });
}

// Avoid: String concatenation
let queryStr = 'SELECT * FROM users WHERE 1=1';
if (filter) {
    queryStr += ` AND status = '${filter}'`; // Unsafe!
}
```

## See also

- [surql](/docs/reference/javascript/api/utilities/surql.md) - Template tag for queries
- [Query](/docs/reference/javascript/api/queries/query.md) - Query execution class
- [SurrealQueryable.query()](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Query method
- [expr](/docs/reference/javascript/api/utilities/expr.md) - Expression builder
