0012. Which semantics a Mesh expression has

0012. Which semantics a Mesh expression has

Status

Proposed

Date

2026-10-04

Deciders

open; the operator or the lead must rule. The recommendation is the roadmap author’s. Provisional until ruled: question Q10 (“SQL’s, documented”) was accepted by the lead without analysis (rulings of 2026-10-04, “Review note”).

Context

One expression tree runs both in memory and in SQL (ADR-0010), so a filter evaluated by the database and a validate evaluated in process can disagree on the same record. The roadmap names the places: nulls, string ordering and division, and wherever SQLite and Postgres themselves differ (roadmap, M4). In SQL, comparing anything with NULL yields “unknown” (three-valued logic); in JavaScript null === null is true.

The central question is that “SQL semantics” is not one thing. Mesh has two SQL adapters in v1 and they disagree. Examples from general database knowledge, not researched in notes/research/: default collation (SQLite compares bytes; Postgres follows the database’s locale), whether LIKE is case sensitive, integer division, and how booleans are stored. M4 acceptance test 1 requires every merged adapter and the in-memory form to give the same answer from one table (roadmap, M4). So wherever the two databases differ, at least one adapter must wrap its native operator. A Postgres collation set by server configuration cannot be reproduced by an in-memory form fixed at build time.

Ash is the precedent for the host-language option, not the SQL one. AshPostgres “installs SQL functions so Elixir semantics hold in the database”, and that cost a filter written with && about 3,400 ms against about 110 ms with and, because the function call defeats the index (research synthesis, section 2.2).

Decision

Not decided.

Recommendation (roadmap author): option A. The function tables of M4 test 1 are the written definition. Blocks: M4, whose tables and in-memory implementations are that definition (roadmap, section 4: M4 needs ADR-0012 ruled). It must be ruled before M4 starts.

Options considered

Option A: Mesh defines the semantics per registered function and operator

Wherever both databases agree natively (three-valued null logic), use the native operator. Wherever one disagrees, wrap the operator in that adapter.

Dimension Assessment
Complexity Medium: a definition per function, plus wrappers where dialects differ
Cost Moderate; paid per function, and again for each new adapter
Query speed Native where dialects agree; wrappers may defeat indexes
Surprise for authors Documented per function; null logic follows SQL

Pros: only pays where a real disagreement exists; the tables prove it.
Cons: the cost of B appears locally, wherever a wrapper is needed. String ordering under a server-configured Postgres collation may not be reproducible in memory; it may have to be excluded, which is option C’s remedy. Every new adapter owes the whole table.

Option B: JavaScript semantics everywhere, forced on both databases

Dimension Assessment
Complexity High: helper SQL functions in each dialect, versioned
Cost High to maintain; a measured run-time cost
Query speed Can be far worse
Surprise for authors Lowest

Pros: authors get what they typed; one definition.
Cons: Ash’s measured cost above applies to every comparison that needs a helper. One measured filter does not rule a semantics out, but it is the only evidence there is.

Option C: A restricted language that rejects what dialects or evaluators disagree on

Dimension Assessment
Complexity Medium: build-time checks on operands
Cost Moderate; rejects some natural expressions
Query speed Native
Surprise for authors Errors at build time, with a fix named

Pros: nothing to wrap or test for the rejected cases; fits “no silent fallback” (roadmap, section 2, item 2). Mesh already knows which attributes are required, so for field references the nullability check is cheaper than it sounds.
Cons: nullability through computed values and function results is unresearched; the language gets smaller.

Trade-off analysis

A and C combine: define semantics per function, and reject at build time what cannot be defined cheaply. B buys uniformity with a measured price. A costs the most in test tables and adapter work; the tables are needed anyway.

Consequences

If A: every function page documents its null behaviour; each adapter ships wrappers for its disagreements; adding a database adapter means passing every table. If unruled, M4 cannot write its tables.

Action items

  • Before M4: ruling by the operator or the lead.
  • M4: per-function null behaviour in the registry; tables with null cases.
  • M4: list the SQLite and Postgres disagreements for the first registry and decide wrap or reject for each.