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.
File Query Language (FQL) is a SQL-like string syntax for querying structured files. It mirrors the fluent API feature-for-feature, and every fluent query can be serialized to FQL by casting it to a string.
use FQL\Query;
$results = Query\Provider::fql(<<<SQL
SELECT *
FROM xml(./products.xml).SHOP.SHOPITEM
WHERE PRICE <= 200
ORDER BY PRICE DESC
LIMIT 10
SQL)->execute()->fetchAll();
FQL supports comments inside query strings. Comments are ignored during parsing and can be used to annotate or temporarily disable parts of a query.
# Single-line comment (hash)
-- Single-line comment (double dash)
/* Multi-line
comment */
SELECT id, name
FROM json(products.json).data.products
WHERE price > 100 -- filter cheap products
/* AND stock > 0 */
ORDER BY price DESC
# or -- starts a single-line comment (everything until end of line is ignored)
/* ... */ encloses a multi-line comment
FileQuery syntax
The FROM, JOIN, and INTO clauses use the FileQuery notation to reference files:
format(pathToFile[, params]).path.to.data
| Part | Description |
|---|
format | Format identifier: xml, json, csv, yaml, neon, ods, xls, log, dir. If omitted, format is detected from the file extension. |
pathToFile | Relative or absolute path to the file (unquoted) |
params | Optional: positional ("value") or named (key: "value"). Cannot be mixed |
path.to.data | Dot-separated path to the data root within the file |
Examples:
xml(feed.xml).SHOP.ITEM
csv(data.csv, "windows-1250", ";").*
csv(data.csv, encoding: "windows-1250", delimiter: ";").*
json(data.json).data.users
log(access.log).*
log(access.log, "apache_common").*
log(access.log, format: "apache_common").*
Default parameter values:
| Format | Parameter | Default |
|---|
| CSV | encoding | utf-8 |
| CSV | delimiter | , |
| CSV | useHeader | 1 |
| XML | encoding | utf-8 |
| LOG | format | nginx_combined |
Field path syntax
Field references use dot notation to access nested data. Advanced path features include:
| Syntax | Description | Example |
|---|
a.b.c | Standard nested access | brand.code |
a.b.0.c | Indexed access | items.0.name |
a.b[].c | Iterated access (all array elements) | items[].price |
a.b.*.c | Wildcard — expands all keys of an associative array | attributes.*.value |
`key.with.dot` | Backtick-escaped key | `Prod. cena`.`s dph` |
Backtick escaping lets you reference keys that contain dots, spaces, or special characters. This syntax works in SELECT, WHERE, ORDER BY, GROUP BY, ON, and function arguments.
SELECT `items`.`Item`.`Prod. cena`.`s dph` AS priceWithVat,
items[].name AS allNames
FROM json(data.json).data
Scalar functions accept [] paths as arguments and operate on the flattened array. For example, LENGTH(products.product[]) returns the number of elements, and IMPLODE(products.product[].name, ', ') joins every nested name into a single string. Backtick-escaped segments work the same way inside function arguments.
Prior to FiQueLa 3.0.2, scalar functions wrapping a [] path returned null because the parser bypassed function detection whenever the field contained []. Upgrade to 3.0.2 or later to use array-iterator paths inside scalar functions.
SELECT
SELECT
[DISTINCT]
select_expr [AS alias] [, select_expr [AS alias]] ...
[EXCLUDE excl_expr [, excl_expr] ...]
select_expr — column name, function call, or literal. Supports dot notation for nested fields. * selects all fields and can be combined with additional expressions (SELECT *, totalPrice).
AS alias — optional alias for the expression.
EXCLUDE — removes the listed fields from the output. Fields excluded this way can still be used in HAVING conditions.
Commas are mandatory between expressions in SELECT, GROUP BY, and ORDER BY clauses. For example, SELECT id, name, price is valid, but SELECT id name price throws an UnexpectedValueException. The fluent API (->select('id, name')) is unaffected by this requirement.
Aliased wildcards
When using source aliases (from FROM ... AS or JOIN ... AS), you can select all fields from a specific aliased source using alias.*:
SELECT p.*, o.total_price
FROM json(products.json).data.products AS p
LEFT JOIN xml(orders.xml).orders.order AS o
ON p.id = o.product_id
If two aliased sources share field names and you use alias.* for both, a SelectException is thrown on ambiguous field conflicts.
Examples:
SELECT id, name, brand.code AS brandCode
FROM json(products.json).data.products
SELECT DISTINCT category
FROM json(products.json).data.products
SELECT
brand.code AS brandCode,
COUNT(id) AS productCount
EXCLUDE productCount
FROM json(products.json).data.products
HAVING productCount > 1
Literal values
You can use quoted literal values directly in SELECT. Literal strings are automatically cast to their appropriate type:
SELECT
"my label" AS label,
"1" AS integerOne,
"1.5" AS floatValue,
"true" AS boolTrue,
"null" AS nullValue
FROM json(products.json).data.products
| Quoted value | Resulting type |
|---|
"1" | int |
"1.5" | float |
"true" / "false" | bool |
"null" | null |
"2025-05-14 12:00:00" | DateTimeImmutable |
"any other text" | string |
You can also reference a previously aliased field in a later expression within the same SELECT:
SELECT
EXPLODE(tags, "|") AS tagList,
tagList[0] AS firstTag
FROM json(products.json).data.products
FROM
FROM file_reference [AS alias]
where file_reference is a FileQuery string. The path inside the FileQuery sets the data root:
FROM csv(./examples/data/products.csv).*
FROM xml(./feed.xml).SHOP.SHOPITEM
FROM json(./data.json).data.users
FROM aliasing
You can alias the FROM source using AS. Aliased fields are then accessible via alias.field dot notation, which is especially useful when joining multiple sources:
SELECT p.id, p.name, p.price
FROM json(products.json).data.products AS p
WHERE p.price > 100
The alias applies to the entire data source. You can combine it with alias.* in SELECT to select all fields from the aliased source.
WHERE and HAVING
[WHERE where_condition [AND|OR|XOR where_condition ...]]
[HAVING where_condition [AND|OR|XOR where_condition ...]]
Condition syntax:
field_expr comparison_operator value_expr
WHERE filters rows before aggregation. HAVING filters after aggregation.
Examples:
WHERE price > 100 AND name LIKE "%widget%"
HAVING total_count > 5 OR total_revenue > 1000
FQL supports parentheses for grouping conditions:
WHERE price > 100
AND (name LIKE "%widget%" OR stock > 0)
See Conditions for all operators.
GROUP BY
[GROUP BY group_expr [, group_expr] ...]
Dot notation is supported for nested fields:
GROUP BY brand.code
GROUP BY category.id, brand.code
ORDER BY
[ORDER BY order_expr [ASC|DESC] [, order_expr [ASC|DESC]] ...]
Dot notation is supported for sorting by nested fields:
ORDER BY price DESC
ORDER BY productCount DESC, totalPrice ASC
ORDER BY brand.code ASC
LIMIT and OFFSET
[LIMIT row_count [OFFSET offset]]
[LIMIT row_count, offset]
LIMIT 10
LIMIT 20 OFFSET 40
LIMIT 20, 40
EXPLAIN and EXPLAIN ANALYZE
Prepend EXPLAIN to return a query execution plan without processing data. Use EXPLAIN ANALYZE to execute the query and collect real row counts and timings.
EXPLAIN
SELECT id, name
FROM json(./products.json).data.products
WHERE price > 100
ORDER BY name DESC
LIMIT 10
EXPLAIN ANALYZE
SELECT id, name
FROM json(./products.json).data.products
WHERE price > 100
ORDER BY name DESC
LIMIT 10
Output columns: phase, rows_in, rows_out, filtered, time_ms, duration_pct, mem_peak_kb, note.
UNION and UNION ALL
select_statement
UNION [ALL]
select_statement
[UNION [ALL] select_statement ...]
UNION removes duplicate rows. UNION ALL keeps all rows. The number of selected columns must match across all queries.
SELECT name, price FROM json(products.json).data.products
WHERE price <= 100
UNION
SELECT name, price FROM json(products.json).data.products
WHERE price >= 400
SELECT name, price FROM xml(./feed1.xml).SHOP.ITEM
WHERE price > 100
UNION
SELECT name, price FROM xml(./feed2.xml).SHOP.ITEM
WHERE price > 200
UNION ALL
SELECT name, price FROM xml(./feed3.xml).SHOP.ITEM
INTO
Export query results to a file using the same FileQuery notation:
SELECT name, price
FROM csv(./products.csv).*
INTO json(./exports/products.json).root.items
The meaning of the .query path in INTO depends on the target format:
| Format | .query meaning | Example |
|---|
| XML | ROOT.ROW — root element and row element | .SHOP.ITEM |
| JSON | Nested key path for the resulting array | .root.items |
| XLSX/ODS | SheetName.StartCell | .Sheet1.B4 |
| CSV | Ignored | — |
| NDJSON | Ignored | — |
Existing target files are not overwritten — an exception is thrown if the file already exists. Missing output directories are created automatically.
DESCRIBE
Use DESCRIBE to inspect the schema of a data source. Returns one row per column with type statistics.
file_reference follows the FileQuery syntax.
DESCRIBE is a standalone statement — it cannot be combined with SELECT, WHERE, GROUP BY, ORDER BY, LIMIT, JOIN, UNION, or EXPLAIN.
Examples:
DESCRIBE json(./examples/data/products.json).data.products
DESCRIBE csv(./examples/data/products.csv).*
Output columns
| Column | Type | Description |
|---|
column | string | Column name (dot notation for nested objects) |
path | string[] | Array of path segments to the column. Useful for correctly evaluating nested fields whose keys contain dots or special characters. |
types | array | Map of type name to occurrence count |
totalRows | int | Number of non-empty rows for this column |
totalTypes | int | Number of distinct types observed |
dominant | string | Most frequent type |
suspicious | bool | true if column has mixed non-empty types (except int+double) |
confidence | float | Ratio of dominant type occurrences to total (0.0–1.0) |
completeness | float | Ratio of non-empty rows to total rows (0.0–1.0) |
constant | bool | true if all non-empty values are identical |
isEnum | bool | true if column has 2–5 unique values |
isUnique | bool | true if all non-empty values are unique |
FQL vs fluent API
SELECT
brand.code AS brandCode,
GROUP_CONCAT(id, "/") AS products,
SUM(price) AS totalPrice,
COUNT(id) AS productCount
FROM json(products.json).data.products
WHERE
price < 300
OR price > 400
GROUP BY brand.code
ORDER BY productCount DESC