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

# ResultsProvider

> The result set returned by Query::execute(). Provides iteration, fetching, aggregation, and export methods.

## Overview

**Namespace:** `FQL\Results`

`ResultsProvider` is the abstract base class for result sets returned by `Query::execute()`. It implements `IteratorAggregate` and provides methods to iterate, fetch individual rows, perform aggregate calculations, and export results to a file.

You never instantiate `ResultsProvider` directly — you receive it from `execute()`.

```php theme={null}
use FQL\Query\Provider;

$results = Provider::fromFile('orders.json')
    ->select('id', 'total', 'status')
    ->where('status', \FQL\Enum\Operator::EQUAL, 'paid')
    ->execute();

echo $results->count();      // 42
echo $results->sum('total'); // 18340.00
```

***

## Subtypes

`Query::execute()` returns one of two concrete implementations depending on query complexity:

| Class                    | When used                                                                                                                                                              |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Results\Stream`         | Simple queries without joins or sorting. Processes data lazily as a generator — low memory usage.                                                                      |
| `Results\InMemory`       | Queries with joins, sorting, or `DISTINCT`. Loads all rows into an `ArrayIterator` before returning.                                                                   |
| `Results\DescribeResult` | Returned by `describe()` queries. Contains one row per column with schema statistics (types, completeness, uniqueness). See [DESCRIBE](/querying/fluent-api#describe). |

<Note>
  You can force a specific mode by passing the class name to `execute()`:

  ```php theme={null}
  use FQL\Results\InMemory;

  $results = $query->execute(InMemory::class);
  ```
</Note>

Both classes expose the same `ResultsProvider` interface described below.

***

## Iteration

### `getIterator()`

Returns a `Traversable` over all result rows. Each row is an associative array.

```php theme={null}
public function getIterator(): \Traversable
```

`ResultsProvider` implements `IteratorAggregate`, so you can use it in a `foreach` loop directly.

```php theme={null}
foreach ($results as $row) {
    echo $row['name'];
}
```

***

## Fetching rows

### `fetchAll()`

Yield all rows as a `Generator`. Optionally hydrate each row into a DTO class.

```php theme={null}
public function fetchAll(?string $dto = null): \Generator
```

<ParamField path="dto" type="string | null">
  Fully qualified class name of a DTO. When provided, each row is mapped to an instance of that class using constructor parameters or public properties.
</ParamField>

<ResponseField name="returns" type="Generator">
  Yields associative arrays (or DTO objects when `$dto` is set).
</ResponseField>

```php theme={null}
foreach ($results->fetchAll() as $row) {
    echo $row['id'] . ': ' . $row['name'];
}

// Hydrate into a DTO
class ProductDto {
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly float $price,
    ) {}
}

foreach ($results->fetchAll(ProductDto::class) as $product) {
    echo $product->name;
}
```

***

### `fetch()`

Return the first row only, or `null` if the result set is empty.

```php theme={null}
public function fetch(?string $dto = null): mixed
```

<ParamField path="dto" type="string | null">
  Optional DTO class name for hydration.
</ParamField>

<ResponseField name="returns" type="array | object | null">
  The first row as an associative array, a DTO object, or `null`.
</ResponseField>

```php theme={null}
$product = $results->fetch();
if ($product !== null) {
    echo $product['name'];
}
```

***

### `fetchSingle()`

Return a single field value from the first row.

```php theme={null}
public function fetchSingle(string $field): mixed
```

<ParamField path="field" type="string" required>
  Field name, including dot-notation for nested fields (e.g. `address.city`).
</ParamField>

<ResponseField name="returns" type="mixed">
  The value of the field, or `null` if the row or field does not exist.
</ResponseField>

```php theme={null}
$name = $results->fetchSingle('name');
$city = $results->fetchSingle('address.city');
```

***

### `fetchNth()`

Yield every nth row, or every even/odd row.

```php theme={null}
public function fetchNth(int|string $n, ?string $dto = null): \Generator
```

<ParamField path="n" type="int | 'even' | 'odd'" required>
  * Integer: yield every nth row (1-indexed count).
  * `'even'`: yield rows at even positions (0-indexed: 0, 2, 4…).
  * `'odd'`: yield rows at odd positions (0-indexed: 1, 3, 5…).
</ParamField>

<ParamField path="dto" type="string | null">
  Optional DTO class name.
</ParamField>

**Throws:** `InvalidArgumentException` for invalid `$n` values.

```php theme={null}
// Every 3rd row
foreach ($results->fetchNth(3) as $row) { ... }

// Even-positioned rows
foreach ($results->fetchNth('even') as $row) { ... }
```

***

### `exists()`

Return `true` if the result set contains at least one row.

```php theme={null}
public function exists(): bool
```

```php theme={null}
if ($results->exists()) {
    echo 'Found records';
}
```

***

## Counting

### `count()`

Return the total number of rows in the result set.

```php theme={null}
public function count(): int
```

<Note>
  `Results\Stream` caches the count internally after the first call, so repeated `count()` calls do not re-read the file.
</Note>

```php theme={null}
echo $results->count(); // e.g. 1024
```

***

## Aggregation

**Interface:** `FQL\Interface\Aggregable`

The `Aggregable` interface defines four aggregate methods: `sum()`, `avg()`, `min()`, and `max()`. It is implemented by `Results\Stream` and `Results\InMemory`.

These methods iterate the result set and compute aggregate values. For `Results\Stream`, computed values are cached so that repeated calls do not re-read the stream.

<Note>
  `DescribeResult` does not implement `Aggregable` and does not provide these methods. If you need aggregation on a describe result, run a separate query.
</Note>

### `sum()`

```php theme={null}
public function sum(string $field): float
```

<ParamField path="field" type="string" required>Field name to sum. Non-numeric values are treated as `0`.</ParamField>

### `avg()`

```php theme={null}
public function avg(string $field, int $decimalPlaces = 2): float
```

<ParamField path="field" type="string" required>Field name to average.</ParamField>
<ParamField path="decimalPlaces" type="int">Number of decimal places in the result (default `2`).</ParamField>

### `min()`

```php theme={null}
public function min(string $field): float
```

<ParamField path="field" type="string" required>Field name to find the minimum of.</ParamField>

### `max()`

```php theme={null}
public function max(string $field): float
```

<ParamField path="field" type="string" required>Field name to find the maximum of.</ParamField>

```php theme={null}
$total   = $results->sum('price');        // 4850.00
$average = $results->avg('price', 2);     // 121.25
$lowest  = $results->min('price');        // 9.99
$highest = $results->max('price');        // 499.00
```

***

## Exporting

### `into()`

Write all result rows to a file. The output format is determined by the file extension in the FileQuery string. Returns a `FileQuery` with the effective query defaults applied, which you can use to read the written file back.

```php theme={null}
public function into(string|FileQuery $target): ?FileQuery
```

<ParamField path="target" type="string | FileQuery" required>
  A file path string (e.g. `output/report.csv`) or a `FileQuery` object. The directory is created automatically if it does not exist.
</ParamField>

<ResponseField name="returns" type="FileQuery | null">
  A `FileQuery` object with defaults applied (ready for reading the written file back), or `null` when running under `EXPLAIN ANALYZE` mode. The returned `FileQuery` contains the effective query path — for example, `*` for CSV/JSON/NDJSON, or `rows.row` for XML.
</ResponseField>

**Throws:** `FileQueryException`, `InvalidFormatException`, `InvalidArgumentException`

```php theme={null}
// Export to JSON — returns FileQuery with effective query
$fileQuery = $results->into('json(output/filtered.json).root.items');

// Export to CSV
$fileQuery = $results->into('csv(output/report.csv)');

// Export to XML
$fileQuery = $results->into('xml(output/report.xml).ROOT.ROW');

// Using a FileQuery object
use FQL\Query\FileQuery;
$fileQuery = $results->into(new FileQuery('csv(exports/products.csv)'));

// Read back the written file using the returned FileQuery
if ($fileQuery !== null) {
    $readBack = \FQL\Query\Provider::fromFileQuery((string) $fileQuery)
        ->selectAll()
        ->execute();
}
```

<Warning>
  Calling aggregation methods (`sum`, `avg`, etc.) before `into()` on a `Results\Stream` will exhaust the generator. Call `into()` first, or use `Results\InMemory`.
</Warning>
