Defining grids (server)
Every grid is backed by a GridDefinition — the single source of truth for
which columns exist, how they map to SQL, which operations are allowed, and which
driver runs the queries. It is also the security boundary: only declared columns
are ever referenced in SQL, and all values are bound.
Creating a definition
use Elyra\DataGrid\Server\Grid;
$grid = Grid::table('sales') // table name
->connection('elyrasql') // a config/database.php connection (default: app default)
->driver('elyrasql') // 'elyrasql' (default) | 'mysql' | 'pgsql' | 'clickhouse' | 'sqlite' | GridDriver instance
->key('id'); // primary key (stable pagination + editing/selection)
Declaring columns
Use ->columns([...]) for quick defaults, and ->column(...) for control:
$grid
->columns(['id', 'store_id', 'region', 'category', 'sku'])
->column('status',
editable: true,
editor: 'select',
rules: 'required|in:pending,paid,shipped,delivered,returned',
)
->column('qty', type: 'number', editable: true, rules: 'required|integer|min:0')
->column('revenue', type: 'number', editable: true, rules: 'required|numeric')
->column('margin', type: 'number')
->searchable(['sku', 'category']);
column() parameters
| Parameter | Type | Default | Purpose |
|---|---|---|---|
key |
string | — | Protocol field name (what clients send). |
column |
?string | = key |
Real SQL column. |
expression |
?string | null | Raw SQL expression (used instead of column, not quoted, not writable). |
sortable |
bool | true | Allow sorting. |
filterable |
bool | true | Allow filtering / facets. |
searchable |
bool | false | Include in global search. |
aggregatable |
?bool | numeric→true | Allow aggregate functions. |
type |
string | string |
string | number | date | bool (formatting/editor hint). |
editable |
bool | false | Write allow-list — only these can be mutated. |
editor |
string | text |
text | number | select | date | checkbox. |
rules |
string|array|null | null | Laravel validation rules applied on edit. |
options |
array | [] |
[['value'=>.., 'label'=>..]] for select editors. |
Computed columns
$grid->column('full_name', expression: "CONCAT(first_name, ' ', last_name)");
Expression columns are read-only (cannot be edited) and are emitted verbatim, so never interpolate user input into them.
Answering requests
use Elyra\DataGrid\Protocol\GridRequest;
$response = $grid->respond(GridRequest::fromArray($request->all()));
respond() returns a GridResponse array: rows,
view, and any opted-in total, groups, aggregates, facets, timing.
Drivers
| Driver | Facets | Percentile | Hybrid search | Full-text |
|---|---|---|---|---|
elyrasql (default) |
FACET() single pass |
✅ | ✅ | ✅ |
mysql (fallback) |
N × GROUP BY |
✖ | ✖ | ✅ (MATCH) |
pgsql |
N × GROUP BY |
✅ (percentile_cont) |
✖ | ✅ (tsvector) |
clickhouse |
N × GROUP BY |
✅ (quantile) |
✖ | ✖ |
sqlite / sqlanywhere |
N × GROUP BY |
✖ | ✖ | ✖ |
Grouping and subtotals are not in the table because they are not driver
features: the engine computes them with one GROUP BY per level, so every driver
gets them (see Grouping).
Grid::table('sales')->driver('mysql'); // any MySQL-compatible server
Grid::table('users')->driver('pgsql') // PostgreSQL
->connection('pgsql');
Grid::table('events')->driver('clickhouse') // ClickHouse (OLAP column store)
->connection('clickhouse');
Grid::table('reports')->driver('sqlite') // SQLite
->connection('sqlite');
Grid::table('reports')->driver('sqlanywhere'); // Elyra SQL Anywhere (SQLite dialect)
Grid::table('sales')->driver(new MyCustomDriver()); // implement Contracts\GridDriver
PostgreSQL. Point ->connection() at a Laravel pgsql connection. Identifiers
are double-quoted ("col"); percentiles map to
percentile_cont(p) WITHIN GROUP (ORDER BY col) and median to the 0.5 variant;
and search: { mode: 'fulltext' } compiles to
to_tsvector('simple', …) @@ plainto_tsquery('simple', ?). HYBRID/vector search
stays ElyraSQL-only.
ClickHouse. Point ->connection() at a ClickHouse connection. Percentiles map
to quantile(p)(col), median to median(col), distinct counts to uniqExact.
LIKE uses ClickHouse's intrinsic backslash escaping (it has no ESCAPE clause),
so it is the one driver whose bound values still escape with \. HYBRID/vector
search stays ElyraSQL-only.
LIKE wildcard escaping
% and _ in a filter or search value are escaped so they match literally — a
search for 50% finds 50% off, not every row. The escape character is !,
declared with ESCAPE '!', and never a backslash.
That is not cosmetic. A literal backslash inside the SQL (ESCAPE '\') breaks
PDO's own placeholder scanner on PHP 8.2 and 8.3: it reads the \ as escaping
the closing quote, loses track of where the string ends, and raises
Invalid parameter number for any placeholder that comes after. Two contains
filters in one query, or a substring search across two columns, is enough. !
has no escaping meaning in any dialect, so the problem cannot arise.
SQLite / SQL Anywhere. sqlite and sqlanywhere share one SQLite-dialect
driver (SQL Anywhere is a SQLite-compatible
engine). Point ->connection() at a Laravel sqlite connection (embedded file
or replica) or any SQLite-compatible PDO. Core features — sorting, filtering
(with correct LIKE wildcard escaping), paging, grouping subtotals, additive
aggregates, editing, and streamed export — all work; the single-pass FACET(),
percentiles, and HYBRID/full-text search are ElyraSQL-only.
Security model
- Clients reference columns by name. Unknown fields are rejected
(
GridException), so a client can never query a column you did not declare. - Sorting/filtering honor per-column
sortable/filterableflags. - All filter/search values are passed as bound parameters.
- Editing only writes columns marked
editable; validationrulesrun first.
Mutations (editing) and export
The same definition powers editing and export:
use Elyra\DataGrid\Protocol\GridMutation;
$result = $grid->mutate(GridMutation::fromArray($request->all())); // create/update/delete
$response = $grid->export(GridRequest::fromArray($r->all()), 'csv'); // StreamedResponse
Full reference
See the Server API reference for every class and method.