Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Elyra
Defining grids (server)

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/filterable flags.
  • All filter/search values are passed as bound parameters.
  • Editing only writes columns marked editable; validation rules run 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

See Editing and Export.

Full reference

See the Server API reference for every class and method.