Raw-SQL views
A raw-SQL view is the escape hatch in the view subsystem: a view whose data source is an
admin/package-authored SELECT statement instead of the single-entity engine. Use it for
cross-entity reporting and master-detail child grids that the entity-driven engine can't express
(arbitrary joins, _lng companions, aggregates).
A view is raw when syntec_view.sql is non-null (and syntec_entity_id is null). The engine wraps
the authored SQL as an inner subquery and layers paging, filtering, sort, global search, and the
optional count on top — all parameterised — so the inner SQL is never parsed or interpolated
(RawViewQuery). The SQL is trusted (it is the source of truth for what it exposes); only the
parameters layered on top are made injection-safe. Who may open the view is still gated by
syntec_view_role.
Declaring columns
A raw view's columns live in syntec_view_column like any view, except each row carries its type in
data_type (varchar, integer, boolean, decimal, reference, text) instead of pointing
at a syntec_field. The col_key is the SQL output alias; labels come from
syntec_view_column_lng. Only declared aliases are selectable, filterable, and sortable — anything
else is dropped, so filters and sort are safe even though the inner SQL is arbitrary.
System placeholders
The engine binds a small fixed vocabulary of named placeholders only when they appear in the SQL — bound as real parameters, never interpolated:
| Placeholder | Type | Value |
|---|---|---|
:lang_id |
int | the current user's resolved language id |
:active |
int | the active status (1) |
:user_id |
int | the current principal's user id |
:parent_guid |
string | the parent form's guid for an embedded subview (see below); '' when there is no parent |
Using a raw-SQL view as a form subview
A raw-SQL view can back a master-detail child grid in a form, scoped to the record being edited —
the One equivalent of Syntec Core's stored-query lists with :guid.
Scope with :parent_guid. Unlike an entity view (which scopes via parent_field — the engine
resolves the parent guid to its row id and adds a parent_field = id filter), a raw view scopes
itself in its own SQL: join to the parent table and compare the guid string.
SELECT c.firstname, c.lastname
FROM syntec_data_child_test c
JOIN syntec_data_column_test p ON c.syntec_data_column_test_id = p.id
WHERE p.guid = :parent_guid AND c.status = :active
Gotcha: raw views bind the parent guid (a uuid string), not an integer id. Your
WHEREmust compare against aguidcolumn (p.guid = :parent_guid), reached by joining to the parent — not against anid.
Wire it to the form. Add an entity_component to the form and point it at the raw view with a
source_ref property (= the view cid) — the same component that embeds an entity child grid:
syntec_form_component component_type = 'entity_component'
syntec_form_component_property key = 'source_ref' value = '<raw-view-cid>'
No admin or SDK change is needed: the embedded grid always sends the parent record's parentGuid
in the query body, and RecordApi::queryView() forwards it into RawViewQuery, which binds
:parent_guid. For a top-level view or an unsaved parent the guid is '', so p.guid = '' matches
no rows and the subview is correctly empty (never an error).
Read-only. A raw view has no entity and no open-form, so it is a reporting grid — there is no row create/edit/delete through it.
Worked example (the local demo package)
The data_child_test_sql raw view above is wired as a Raw SQL subview tab on
data_column_test_form (component entity_component, source_ref = data_child_test_sql). Opening a
Data-column-test record lists exactly that record's children, scoped by :parent_guid. It ships in
the local (demo) package — provision it with syntec:package:import local.