> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oleander.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Query

> Run and submit queries through the oleander query router.

The SDK has two query methods and one legacy one. `queryRun` reads, `querySubmit` writes, and both go through the [query router](/platform/query-routing), which picks the engine and machine size for you.

## `queryRun(sqlOrOptions)`

Runs a query and returns the rows on the call.

oleander parses the SQL, estimates how much data the referenced tables hold, and picks the engine (`duckdb`, `polars`, `bloom`) and machine size to match, so leave `engine` as `auto` unless you want a specific one. The choice and the reasoning behind it come back in `engine_decision`.

```ts theme={null}
const result = await oleander.queryRun(
  "SELECT * FROM oleander.default.flowers LIMIT 10",
);

console.log(result.results?.columns, result.results?.rows);
console.log(result.row_count, result.execution_time);
console.log(result.engine_decision?.engine, result.engine_decision?.reasons);
```

`queryRun` is read-only. SQL that could change data is rejected before the request goes out - use `querySubmit` for those.

### Explain without running

Pass `explain: true` to see which engine a query would take, its estimated input size, and whether your plan allows it, without running anything or spending compute.

```ts theme={null}
const { engine_decision } = await oleander.queryRun({
  sql: "SELECT * FROM oleander.default.events",
  explain: true,
});

console.log(engine_decision?.engine, engine_decision?.size_band);
```

Do this before a query you expect to be large.

### Polars scripts

Pass `script` instead of `sql` to run a Polars DataFrame script that assigns `result`, listing the tables it reads in `tables`. This forces the Polars engine.

```ts theme={null}
const scripted = await oleander.queryRun({
  script: "result = events.group_by('day').len()",
  tables: [{ alias: "events", table: "default.events" }],
});
```

### Parameters

<ParamField body="sql" type="string">
  The SQL query to run. Pass a bare string as the only argument for the common case, or use the options object for anything else. Mutually exclusive with `script`.
</ParamField>

<ParamField body="script" type="string">
  A Polars DataFrame script that assigns `result`. Requires `tables`.
</ParamField>

<ParamField body="tables" type="QueryTable[]">
  Tables the script reads, as `{ alias, table }`.
</ParamField>

<ParamField body="engine" type="&#x22;auto&#x22; | &#x22;duckdb&#x22; | &#x22;polars&#x22; | &#x22;bloom&#x22;" default="&#x22;auto&#x22;">
  Ask for a specific engine. An impossible combination returns an engine capability error rather than rerouting.
</ParamField>

<ParamField body="explain" type="boolean" default="false">
  Return the routing decision without executing.
</ParamField>

## `querySubmit(options)`

Covers everything that changes data: a `SELECT` plus a `destination` to write it to, and a statement that names its own target (`INSERT`, `UPDATE`, `DELETE`, `MERGE`, DDL) with no destination. It also takes reads too large to return interactively.

Whether the write finishes on the call depends on the engine the router picked, so read `state`. Result rows are never returned, only `row_count` when it is known.

```ts theme={null}
const submitted = await oleander.querySubmit({
  sql: "SELECT day, count(*) AS n FROM oleander.default.events GROUP BY day",
  destination: "default.daily_counts",
  writeMode: "overwrite", // or "append"
});

console.log(submitted.state, submitted.output_table, submitted.run_id);
```

| `state`     | Meaning                                                                  |
| ----------- | ------------------------------------------------------------------------ |
| `COMPLETE`  | The write already landed. Nothing to poll.                               |
| `SUBMITTED` | A job is running. `run_id` is returned and the write is not visible yet. |

### `querySubmitAndWait(options)`

Submits and polls until an asynchronous run finishes. A write that lands inline returns immediately with no `run`.

```ts theme={null}
const { state, run } = await oleander.querySubmitAndWait({
  sql: "INSERT INTO oleander.default.daily_counts SELECT * FROM staging.daily",
});

console.log(state); // COMPLETE | FAIL | ABORT
```

### Parameters

<ParamField body="sql" type="string">
  The statement to run. With a `destination`, a `SELECT`; without one, a statement that names its own target.
</ParamField>

<ParamField body="destination" type="string">
  Table to write the result to, as `namespace.table`.
</ParamField>

<ParamField body="writeMode" type="&#x22;overwrite&#x22; | &#x22;append&#x22;" default="&#x22;overwrite&#x22;">
  Applies to a `destination` write. A statement that names its own target carries its own semantics.
</ParamField>

<ParamField body="engine" type="&#x22;auto&#x22; | &#x22;duckdb&#x22; | &#x22;polars&#x22; | &#x22;bloom&#x22; | &#x22;spark&#x22;" default="&#x22;auto&#x22;">
  Ask for a specific engine.
</ParamField>

<ParamField body="explain" type="boolean" default="false">
  Return the routing decision without submitting anything.
</ParamField>

## `query(sql, options?)`

<Warning>
  `query` predates the router. It always runs DuckDB, sized by the org's default sandbox setting rather than by the query. Prefer `queryRun`; reach for this only when you need `save: true`.
</Warning>

Executes SQL against the lake on DuckDB, with optional auto-save by query hash. If the API rejects the query, the SDK throws instead of returning a failed result.

```ts theme={null}
const result = await oleander.query(
  "SELECT * FROM oleander.default.flowers LIMIT 10",
  { save: true },
);

if (result.saved_table_name) console.log("Saved to:", result.saved_table_name);
```

### Parameters

<ParamField body="sql" type="string" required>
  The SQL query to execute. Supports DuckDB SQL syntax.
</ParamField>

<ParamField body="options.save" type="boolean" default="false">
  When `true`, persists query results as a table. The table name is returned in `saved_table_name`.
</ParamField>

## Return fields

| Field              | Type      | Description                                                                     |
| ------------------ | --------- | ------------------------------------------------------------------------------- |
| `success`          | `boolean` | Whether the query executed successfully                                         |
| `results`          | `object`  | `columns`, `column_types`, and `rows`. Reads only.                              |
| `row_count`        | `number`  | Number of rows returned or written                                              |
| `execution_time`   | `string`  | Execution time, for example `"42ms"`                                            |
| `engine_decision`  | `object`  | `engine`, `reasons`, `size_band`, `sandbox`. `queryRun` and `querySubmit` only. |
| `compute`          | `object`  | The sandbox that actually served the run - the machine the org is billed for    |
| `state`            | `string`  | `COMPLETE` or `SUBMITTED`. `querySubmit` only.                                  |
| `run_id`           | `string`  | Run to poll when `state` is `SUBMITTED`                                         |
| `output_table`     | `string`  | Destination table for a write                                                   |
| `saved_table_name` | `string`  | Table name if `save: true` was used on `query`                                  |

## Iterating over results

```ts theme={null}
const result = await oleander.queryRun(
  "SELECT species, sepal_length FROM oleander.default.flowers LIMIT 10",
);

const { columns, rows } = result.results!;
const speciesIdx = columns.indexOf("species");
const sepalLengthIdx = columns.indexOf("sepal_length");

for (const row of rows) {
  const species = row[speciesIdx] as string;
  const sepalLength = row[sepalLengthIdx] as number;
  // process each record ...
}
```
