Skip to main content

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();

Comments

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
PartDescription
formatFormat identifier: xml, json, csv, yaml, neon, ods, xls, log, dir. If omitted, format is detected from the file extension.
pathToFileRelative or absolute path to the file (unquoted)
paramsOptional: positional ("value") or named (key: "value"). Cannot be mixed
path.to.dataDot-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:
FormatParameterDefault
CSVencodingutf-8
CSVdelimiter,
CSVuseHeader1
XMLencodingutf-8
LOGformatnginx_combined

Field path syntax

Field references use dot notation to access nested data. Advanced path features include:
SyntaxDescriptionExample
a.b.cStandard nested accessbrand.code
a.b.0.cIndexed accessitems.0.name
a.b[].cIterated access (all array elements)items[].price
a.b.*.cWildcard — expands all keys of an associative arrayattributes.*.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 valueResulting 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 meaningExample
XMLROOT.ROW — root element and row element.SHOP.ITEM
JSONNested key path for the resulting array.root.items
XLSX/ODSSheetName.StartCell.Sheet1.B4
CSVIgnored
NDJSONIgnored
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.
DESCRIBE file_reference
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

ColumnTypeDescription
columnstringColumn name (dot notation for nested objects)
pathstring[]Array of path segments to the column. Useful for correctly evaluating nested fields whose keys contain dots or special characters.
typesarrayMap of type name to occurrence count
totalRowsintNumber of non-empty rows for this column
totalTypesintNumber of distinct types observed
dominantstringMost frequent type
suspiciousbooltrue if column has mixed non-empty types (except int+double)
confidencefloatRatio of dominant type occurrences to total (0.0–1.0)
completenessfloatRatio of non-empty rows to total rows (0.0–1.0)
constantbooltrue if all non-empty values are identical
isEnumbooltrue if column has 2–5 unique values
isUniquebooltrue 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