> For the complete documentation index, see [llms.txt](https://docs.soapjs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.soapjs.com/examples/database-interaction-strategies/using-native-queries.md).

# Using Native Queries

When a query can't be expressed with the `Where` builder or a `RepositoryQuery` (aggregations, geo queries, full-text ranking, vendor-specific operators, raw SQL), SoapJS gives you a **sanctioned escape hatch**: `Source.native()`.

Instead of reaching around the repository to the raw database driver, you wrap the native query in a **named `RepositoryQuery`** and hand it to the source. The query stays quarantined in one place, the rest of your code keeps depending on the abstraction.

> Available since `@soapjs/soap` 0.10.0 (interface) and `@soapjs/soap-node-mongo` 0.7.0 (MongoDB implementation).

### The pattern

`Source.native()` executes a client-specific query **verbatim**. The payload is produced by a `RepositoryQuery`'s `build()` — so the dialect (a MongoDB pipeline, a SQL string) lives in a named query object, not scattered through your code.

```typescript
interface Source<DocumentType> {
  // ...find / count / aggregate / update / insert / remove
  native<ResultType = DocumentType[]>(query: DbQuery | RepositoryQuery): Promise<ResultType>;
}
```

`native()` **bypasses the query factory and the entity mapper** — it returns the raw result from the driver. You map it yourself if you need domain entities.

### Example — a MongoDB aggregation

Define the native query as a `RepositoryQuery`. Its `build()` returns the raw MongoDB pipeline:

```typescript
import { RepositoryQuery } from '@soapjs/soap';

export interface UniverseStatRow {
  _id: { universe: string; alignment: string };
  count: number;
}

export class UniverseStatsAggregation extends RepositoryQuery<Record<string, unknown>[]> {
  build(): Record<string, unknown>[] {
    return [
      { $group: { _id: { universe: '$universe', alignment: '$alignment' }, count: { $sum: 1 } } },
      { $sort: { '_id.universe': 1, '_id.alignment': 1 } },
    ];
  }
}
```

Run it from a repository method through the source — no raw `Collection` handle:

```typescript
async statsByUniverse(): Promise<Result<UniverseStat[]>> {
  const rows = await this.context.source.native<UniverseStatRow[]>(new UniverseStatsAggregation());
  return Result.withSuccess(
    rows.map((r) => ({ universe: r._id.universe, alignment: r._id.alignment, count: r.count })),
  );
}
```

#### How the MongoDB source dispatches the payload

`MongoSource.native()` recognizes the shape produced by `build()`:

| `build()` returns                           | Runs as                                       |
| ------------------------------------------- | --------------------------------------------- |
| an **array**                                | aggregation pipeline (`collection.aggregate`) |
| `{ kind: 'aggregate', pipeline, options? }` | aggregation                                   |
| `{ kind: 'find', filter, options? }`        | `collection.find`                             |
| `{ kind: 'command', command }`              | `db.command`                                  |

### Other databases

`native()` is part of the `Source` contract, so every adapter implements it for its own dialect. A SQL source, for example, would accept a `RepositoryQuery` whose `build()` returns a parameterized statement:

```typescript
export class TopSellersQuery extends RepositoryQuery<{ sql: string; params: unknown[] }> {
  build() {
    return { sql: 'SELECT * FROM orders WHERE total > $1 ORDER BY total DESC', params: [1000] };
  }
}
```

### Guidelines

* **Keep it named and quarantined.** A native query belongs in a dedicated `RepositoryQuery` and is called from one repository method — never smuggled through `as any` in a generic query.
* **It is dialect-specific by design.** Swapping the database means rewriting the native query object; the repository method signature and the rest of your code stay the same.
* **It returns raw rows.** `native()` skips the mapper — map to domain entities in the repository method if needed.
* **Reach for it last.** Prefer `Where` (see *Using Where Conditions*) and `RepositoryQuery` (see *Using QueryBuilder*); use `native()` only when the abstraction genuinely can't express the query.
