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.

The fluent API lets you build queries by chaining PHP method calls. Every method returns the query object, so calls can be composed in any order.
As of FiQueLa 3.0, the fluent helpers are expression-first: select, where, having, groupBy, orderBy, and every scalar / aggregate helper accept full SQL expression strings. They route their field arguments through the same parser that powers FQL, so $query->select('ROUND(price * 1.21, 2) AS gross'), $query->groupBy('YEAR(date)'), $query->where('LOWER(name)', Op::EQ, 'alice'), and $query->sum('price + vat') all work end-to-end. Infix arithmetic (+ - * / %) and nested function calls are valid in every clause.
use FQL\Query;
use FQL\Enum\Operator;

$results = Query\Provider::fromFileQuery('json(./products.json).data.products')
    ->select('id', 'name', 'brand.code')
    ->where('price', Operator::GREATER_THAN, 100)
    ->orderBy('price')->desc()
    ->limit(20)
    ->execute()
    ->fetchAll();
Every query implements \Stringable. Cast a query to a string to see the equivalent FQL representation — useful for debugging.
echo (string) $query;

Field path syntax

FiQueLa uses a dot-separated path to reference fields in nested data structures. Beyond simple dot access, the path syntax supports several advanced features:
SyntaxDescriptionExample
a.b.cStandard nested accessbrand.code
a.b.0.cIndexed access (numeric key)items.0.name
a.b[].cIterated access — traverses all elements of an arrayitems[].price
a.b.*.cWildcard — expands all keys of an associative arrayattributes.*.value
`key.with.dot`Backtick-escaped key — treats the enclosed string as a single key`Prod. cena`.`s dph`
x[0]Index into a scalar or wrapped valuetags[0]
Backtick escaping is useful when a key contains dots, spaces, or other special characters that would otherwise be interpreted as path separators.
// Standard nested access
$query->select('brand.code')->as('brandCode');

// Iterated access — collect a value from every element in an array
$query->select('items[].price')->as('prices');

// Escaped key containing a dot
$query->select('`price.vat`')->as('priceVat');
-- FQL equivalent
SELECT brand.code AS brandCode,
       items[].price AS prices,
       `price.vat` AS priceVat
FROM json(products.json).data.products
This path syntax works everywhere a field name is expected: select(), where(), orderBy(), groupBy(), on(), and inside function arguments. Scalar functions accept [] paths and operate on the flattened array — for example, $query->length('products.product[]') returns the element count, and $query->implode('products.product[].name', ', ') joins every nested name.
Prior to FiQueLa 3.0.2, scalar functions wrapping a [] path returned null. Upgrade to 3.0.2 or later to use array-iterator paths inside scalar functions.

Selecting fields

Use select() to specify which fields to include. Dot notation accesses nested fields. Multiple select() calls merge their fields.
// All equivalent
$query->select('id, name, address.city, address.state');
$query->select('id', 'name', 'address.city', 'address.state');
$query->select('id', 'name')->select('address.city', 'address.state');
Use selectAll() to select all fields (equivalent to SELECT *). You can combine it with additional fields:
$query->selectAll()->select('totalPrice');

Aliases

Chain as() after select(), from(), or a join method to create an alias. The as() method is context-aware — it detects what was called before it and aliases accordingly:
ContextExampleEffect
After select()->select('id')->as('clientId')Aliases the last selected field
After from()->from('data.products')->as('p')Aliases the FROM source
After join()->leftJoin($query)->as('o')Aliases the joined source
$query->select('id')->as('clientId');
$query->select('brand.code')->as('brandCode');
When used after select(), as() only aliases the last field in the preceding call. For example, select('id', 'name')->as('o') creates id, name AS o.

DISTINCT

Remove duplicate rows with distinct():
$query->select('category')->distinct();

EXCLUDE

Use exclude() to remove fields from the output — useful when applying functions and you want only the computed result:
$query->select('id', 'name')
    ->round('totalPrice', 2)->as('finalPrice')
    ->exclude('totalPrice');

// Output: id, name, finalPrice (totalPrice removed)

Aliased wildcards

When a source has an alias (from from()->as() or a join), use alias.* in select() to select all fields from that specific source:
$query->from('data.products')->as('p')
    ->select('p.*')
    ->leftJoin($orders)->as('o')
        ->on('p.id', Operator::EQUAL, 'product_id')
    ->select('o.total_price')->as('totalPrice');
If two aliased sources share field names and you use alias.* for both, a SelectException is thrown on ambiguous field conflicts.

Specifying the data path

Use from() to point to the data root within the file. Dot notation traverses nested structures:
$query->from('data.products');
$query->from('SHOP.SHOPITEM');
When using Query\Provider::fromFileQuery(), the path embedded in the FileQuery string sets the initial from.

FROM aliasing

Chain as() after from() to alias the data source. Aliased fields are then accessible via alias.field dot notation:
$query->from('data.products')->as('p')
    ->select('p.id', 'p.name', 'p.price')
    ->where('p.price', Operator::GREATER_THAN, 100);
This is equivalent to the FQL syntax FROM json(products.json).data.products AS p.

Pagination and limits

// Return at most 20 rows starting from row 40
$query->offset(40)->limit(20);

// Page-based helper — page 2, 20 rows per page
$query->page(2, perPage: 20);

Sorting

Chain orderBy() with asc() or desc(). Multiple orderBy() calls define a multi-column sort. Dot notation is supported for sorting by nested fields.
use FQL\Enum\Sort;

$query->orderBy('price')->desc();
$query->orderBy('price', Sort::ASC)->orderBy('name', Sort::DESC);

// Sort by nested field
$query->orderBy('brand.code')->asc();

Conditions

Filter rows with where(), then chain and(), or(), or xor():
use FQL\Enum\Operator;

$query->where('price', Operator::GREATER_THAN, 100)
    ->and('description', Operator::LIKE, '%wireless%')
    ->or('price', Operator::BETWEEN, [300, 500]);
See Conditions for the full operator reference and condition grouping.

Joins

Join other files or queries using innerJoin(), leftJoin(), rightJoin(), or fullJoin(), followed by on() to define the join condition. You can pass the alias as a second parameter or chain ->as() fluently:
$orders = Query\Provider::fromFileQuery('xml(orders.xml).orders.order');

// Alias as parameter
$query->leftJoin($orders, 'o')
    ->on('id', Operator::EQUAL, 'user_id');

// Alias via as() — equivalent
$query->leftJoin($orders)->as('o')
    ->on('id', Operator::EQUAL, 'user_id');
See Joining data sources for full details.

Grouping and aggregations

Group rows with groupBy() and apply aggregate functions:
$query->count('id')->as('total')
    ->sum('price')->as('revenue')
    ->groupBy('category.id')
    ->having('total', Operator::GREATER_THAN, 10);
See Grouping and aggregations for all aggregate functions.

UNION

Combine results from multiple queries:
// Remove duplicates
$results = $query1->union($query2)->execute();

// Keep all rows
$results = $query1->unionAll($query2)->execute();
The number of selected columns must match across all combined queries (queries using SELECT * skip this check).

EXPLAIN

Inspect the query execution plan without running the full query, or run it with timing data:
// Plan only — no data processed
$plan = $query->explain()->execute();

// Execute and collect real row counts and timings
$plan = $query->explainAnalyze()->execute();
See EXPLAIN & benchmarking for the full column reference.

DESCRIBE

Use describe() to inspect the schema of a data source instead of querying its data. The result is a DescribeResult containing one row per column with type statistics, completeness, and uniqueness information. DESCRIBE is mutually exclusive with SELECT, WHERE, GROUP BY, ORDER BY, LIMIT, JOIN, UNION, and EXPLAIN. Calling any of these after describe() throws a QueryLogicException.
use FQL\Query\Provider;
use FQL\Stream\Json;

$result = Json::open('products.json')->query()
    ->from('data.products')
    ->describe()
    ->execute();

foreach ($result->fetchAll() as $col) {
    echo $col['column'] . ' => ' . $col['dominant'] . PHP_EOL;
}

echo 'Source rows: ' . $result->getSourceRowCount();

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

Detected types

TypeDescription
intInteger value
doubleFloat/double value
stringNon-empty string
boolBoolean value
arrayIndexed array
nullNull value
empty-stringEmpty string ""
whitespaceString containing only whitespace
bool-stringString "yes" or "no"
date-stringISO 8601 date/datetime string
DESCRIBE cannot be combined with other query clauses. Attempting to chain select(), where(), groupBy(), orderBy(), limit(), join(), union(), or explain() with describe() throws a QueryLogicException.

Fluent API vs FQL string

use FQL\Query;
use FQL\Enum\Operator;

$query = Query\Provider::fromFileQuery('json(products.json).data.products')
    ->select('brand.code')->as('brandCode')
    ->groupConcat('id', '/')->as('products')
    ->sum('price')->as('totalPrice')
    ->count('id')->as('productCount')
    ->where('price', Operator::LESS_THAN, 300)
    ->or('price', Operator::GREATER_THAN, 400)
    ->groupBy('brand.code')
    ->orderBy('productCount')->desc();
Casting the fluent query to a string produces the FQL representation, so you can always inspect what SQL-like string a fluent query corresponds to.