> ## 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.

# Supported formats

> Every file format FiQueLa can read, with class names, capabilities, and code examples for opening each one.

FiQueLa supports ten file formats through dedicated stream classes. Each format is identified by a short key used in FQL syntax and in the `Enum\Format` enum.

## Format table

| Key        | Name                   | Class                   | File | String |
| ---------- | ---------------------- | ----------------------- | ---- | ------ |
| `csv`      | CSV                    | `FQL\Stream\Csv`        | Yes  | No     |
| `xml`      | XML                    | `FQL\Stream\Xml`        | Yes  | No     |
| `xls`      | XLSX                   | `FQL\Stream\Xls`        | Yes  | No     |
| `ods`      | ODS                    | `FQL\Stream\Ods`        | Yes  | No     |
| `jsonFile` | JSON stream            | `FQL\Stream\JsonStream` | Yes  | No     |
| `ndJson`   | Newline-delimited JSON | `FQL\Stream\NDJson`     | Yes  | No     |
| `json`     | JSON (`json_decode`)   | `FQL\Stream\Json`       | Yes  | Yes    |
| `yaml`     | YAML                   | `FQL\Stream\Yaml`       | Yes  | Yes    |
| `neon`     | NEON                   | `FQL\Stream\Neon`       | Yes  | Yes    |
| `log`      | HTTP Access Log        | `FQL\Stream\AccessLog`  | Yes  | No     |

`json`, `yaml`, and `neon` also accept raw strings via `Stream\Provider::fromString()`. All other formats require a file path.

## Opening formats

You can open any format in three ways:

1. **`Stream\Provider::fromFile()`** — auto-detects format from the file extension, or accepts an explicit `Enum\Format`.
2. **Direct class `open()`** — call the class for the specific format directly.
3. **FQL string syntax** — embed the format and path in an FQL `FROM` clause.

<Tabs>
  <Tab title="CSV">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from extension
    $csv = Stream\Provider::fromFile('products.csv');

    // Explicit format
    $csv = Stream\Provider::fromFile('data.txt', Format::CSV);

    // Direct class
    $csv = Stream\Csv::open('products.csv');

    // With custom delimiter
    $csv = Stream\Csv::openWithDelimiter('products.csv', ';');

    $query = $csv->query()->selectAll()->from('*');
    ```

    <Note>
      CSV supports format-specific parameters: `encoding` (default `utf-8`), `delimiter` (default `,`), `enclosure` (default `"`), `useHeader` (default `1`), and `bom` (default `0`). Pass them via the FQL string syntax: `csv(data.csv, "windows-1250", ";")` or with named parameters such as `csv(data.csv, enclosure: "'", bom: "1")`. CSV is read and written with native PHP `fgetcsv` / `fputcsv` and detects UTF-8, UTF-16, and UTF-32 BOMs automatically.
    </Note>
  </Tab>

  <Tab title="XML">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from extension
    $xml = Stream\Provider::fromFile('feed.xml');

    // Explicit format
    $xml = Stream\Provider::fromFile('data', Format::XML);

    // Direct class
    $xml = Stream\Xml::open('feed.xml');

    // With explicit encoding
    $xml = Stream\Xml::openWithEncoding('feed.xml', 'windows-1250');

    $query = $xml->query()->selectAll()->from('SHOP.SHOPITEM');
    ```

    <Note>
      XML supports the `encoding` parameter (default `utf-8`). Specify it in FQL syntax: `xml(feed.xml, "windows-1250")`.
    </Note>
  </Tab>

  <Tab title="XLSX">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from .xlsx extension
    $xls = Stream\Provider::fromFile('report.xlsx');

    // Direct class
    $xls = Stream\Xls::open('report.xlsx');

    $query = $xls->query()->selectAll()->from('*');
    ```

    <Note>
      Legacy `.xls` (Excel 97–2003 binary format) is no longer supported. Use `.xlsx` files instead. The underlying library is `openspout/openspout`.
    </Note>
  </Tab>

  <Tab title="ODS">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from extension
    $ods = Stream\Provider::fromFile('spreadsheet.ods');

    // Direct class
    $ods = Stream\Ods::open('spreadsheet.ods');

    $query = $ods->query()->selectAll()->from('*');
    ```
  </Tab>

  <Tab title="JSON stream">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect: .json and .jsonfile extensions map to JsonStream
    $json = Stream\Provider::fromFile('large-dataset.json');

    // Explicit format key
    $json = Stream\Provider::fromFile('data', Format::JSON_STREAM);

    // Direct class
    $json = Stream\JsonStream::open('large-dataset.json');

    $query = $json->query()->selectAll()->from('data.items');
    ```

    <Tip>
      `JsonStream` uses `halaxa/json-machine` for streaming, making it suitable for large JSON files where loading the entire document into memory would be impractical.
    </Tip>
  </Tab>

  <Tab title="NDJSON">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from .ndjson extension
    $ndjson = Stream\Provider::fromFile('events.ndjson');

    // Direct class
    $ndjson = Stream\NDJson::open('events.ndjson');

    $query = $ndjson->query()->selectAll()->from('*');
    ```
  </Tab>

  <Tab title="JSON (in-memory)">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // From file — loads entire document via json_decode
    $json = Stream\Provider::fromFile('config.json', Format::JSON);

    // Direct class
    $json = Stream\Json::open('config.json');

    // From string
    $json = Stream\Provider::fromString(
        '{"users":[{"id":1,"name":"Alice"}]}',
        Format::JSON
    );

    $query = $json->query()->selectAll()->from('users');
    ```
  </Tab>

  <Tab title="YAML">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from .yaml or .yml extension
    $yaml = Stream\Provider::fromFile('config.yaml');

    // Direct class
    $yaml = Stream\Yaml::open('config.yaml');

    // From string
    $yaml = Stream\Provider::fromString(
        "users:\n  - id: 1\n    name: Alice",
        Format::YAML
    );

    $query = $yaml->query()->selectAll()->from('users');
    ```
  </Tab>

  <Tab title="NEON">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from .neon extension
    $neon = Stream\Provider::fromFile('config.neon');

    // Direct class
    $neon = Stream\Neon::open('config.neon');

    // From string
    $neon = Stream\Provider::fromString(
        "users:\n\t- id: 1\n\t  name: Alice",
        Format::NEON
    );

    $query = $neon->query()->selectAll()->from('users');
    ```
  </Tab>

  <Tab title="HTTP access log">
    ```php theme={null}
    use FQL\Enum\Format;
    use FQL\Stream;

    // Auto-detect from .log extension
    $log = Stream\Provider::fromFile('access.log');

    // Explicit format
    $log = Stream\Provider::fromFile('access.log', Format::LOG);

    // Direct class — uses nginx_combined profile by default
    $log = Stream\AccessLog::open('access.log');

    // Switch to a different log profile
    $log->setFormat('apache_common');

    $query = $log->query()->selectAll()->from('*');
    ```

    <Note>
      The `log` format accepts a `format` parameter (default `nginx_combined`) to select a predefined log profile. Use `log(access.log, "apache_common")` in FQL syntax or pass `format: "custom"` with a `pattern` parameter for custom Apache `log_format` patterns.
    </Note>
  </Tab>
</Tabs>

## Format-specific parameters

CSV, XML, and LOG accept additional configuration parameters.

### CSV parameters

| Parameter   | Default | Description                                                                                                                               |
| ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `encoding`  | `utf-8` | Input file encoding. Any encoding accepted by `iconv`. Non-UTF-8 input is transcoded via a `convert.iconv.<src>/UTF-8` PHP stream filter. |
| `delimiter` | `,`     | Single-character field separator.                                                                                                         |
| `enclosure` | `"`     | Single-character field enclosure used by `fgetcsv` / `fputcsv`.                                                                           |
| `useHeader` | `1`     | `1` to treat the first row as column headers, `0` otherwise.                                                                              |
| `bom`       | `0`     | `1` to emit a UTF-8 BOM when writing. The reader detects UTF-8, UTF-16 LE/BE, and UTF-32 LE/BE BOMs automatically and skips them.         |

In FQL string syntax:

```sql theme={null}
-- Positional
FROM csv(data.csv, "windows-1250", ";")

-- Named
FROM csv(data.csv, encoding: "windows-1250", delimiter: ";", enclosure: "'", bom: "1")
```

<Note>
  As of FiQueLa **3.0**, CSV runs on native PHP primitives — the previous `league/csv` dependency has been dropped. Type coercion is lazy: cells stay as strings until a comparison or arithmetic operation requires a typed value, and empty cells satisfy `IS NULL`.
</Note>

### XML parameters

| Parameter  | Default | Description          |
| ---------- | ------- | -------------------- |
| `encoding` | `utf-8` | Input file encoding. |

In FQL string syntax:

```sql theme={null}
FROM xml(feed.xml, "windows-1250")
```

#### Empty XML elements

Empty leaf elements (`<foo/>` or `<foo></foo>`) surface as an empty string `''`, so SQL-style predicates work directly:

```sql theme={null}
WHERE invoiceNumber IS NULL          -- matches empty <invoiceNumber/>
WHERE invoiceNumber = ''             -- matches empty <invoiceNumber/>
SELECT IF(invoiceNumber IS NULL, '', invoiceNumber) AS invoice
```

Elements with attributes (`<foo id="1"/>`) still expose their `@attributes` structure, mixed content (`<foo id="1">text</foo>`) still surfaces text under the `value` key, and populated leaves (`<foo>text</foo>`) still return their text content.

<Note>
  Prior to FiQueLa **3.0.1**, empty leaf elements surfaced as an empty array `[]`, which forced consumers to probe with `IS ARRAY` or `is_array()` to detect missing values. From 3.0.1 onward, they behave as empty strings — use `IS NULL`, `= ''`, or `IF(field IS NULL, …)` instead.
</Note>

### LOG parameters

| Parameter | Default          | Description                                                                     |
| --------- | ---------------- | ------------------------------------------------------------------------------- |
| `format`  | `nginx_combined` | Log format profile name or `custom` for a custom pattern.                       |
| `pattern` | —                | Custom Apache `log_format` pattern string. Only used when `format` is `custom`. |

**Predefined profiles:**

| Profile           | Pattern                                                             |
| ----------------- | ------------------------------------------------------------------- |
| `nginx_combined`  | `%h - %u [%t] "%r" %>s %b "%{Referer}i" "%{User-Agent}i"` (default) |
| `nginx_main`      | `%h - %u [%t] "%r" %>s %b`                                          |
| `apache_combined` | `%h %l %u [%t] "%r" %>s %b "%{Referer}i" "%{User-Agent}i"`          |
| `apache_common`   | `%h %l %u [%t] "%r" %>s %b`                                         |

In FQL string syntax:

```sql theme={null}
-- Default profile (nginx_combined)
FROM log(access.log).*

-- Positional profile selection
FROM log(access.log, "apache_common").*

-- Named parameter profile selection
FROM log(access.log, format: "apache_common").*

-- Custom pattern
FROM log(access.log, format: "custom", pattern: "%h %l %u [%t] \"%r\" %>s %b").*
```

**Automatic value normalization:**

* `status` is cast to integer
* `time` is converted to `Y-m-d H:i:s` format
* `%D` (request time in microseconds) is converted to milliseconds
* `%r` (request line) is split into `method`, `path`, and `protocol` fields

**Special fields in every row:**

| Field    | Type           | Description                                       |
| -------- | -------------- | ------------------------------------------------- |
| `_raw`   | string         | Original unparsed log line                        |
| `_error` | string \| null | `null` on success, error message on parse failure |

<Note>
  Malformed log lines do not throw exceptions. Instead, they produce a row with `_error` containing the error message and `_raw` containing the original line. This allows you to filter or inspect bad lines with standard FQL conditions.
</Note>

## Directory provider

`FQL\Stream\Dir` is a special provider that treats a directory as its data source. It lets you query metadata about files recursively across a directory tree.

```php theme={null}
use FQL\Stream;

$dir = Stream\Dir::open('./data');

$query = $dir->query()
    ->selectAll()
    ->from('*');
```

In FQL string syntax, use the `dir` format key:

```sql theme={null}
SELECT *
FROM dir(./data).*
```
