zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Databases

Gel (EdgeDB)TODO

🚧 TODO — not yet implemented. This capability is on the roadmap and is not an anti-pattern for zmdb; it simply isn't built yet. refused: Gel owns its schema, so zmdb would be a client rather than the source; the SQL endpoint is the supported path Track / contribute via the issue tracker.

ToDo / not planned. No native Gel (formerly EdgeDB) target ships. The accepted feasibility decision refused it because Gel owns its schema and migration model, while zmdb's declaration is designed to be the source of those artifacts. Gel's PostgreSQL-compatible SQL endpoint is the usable path today.

The supported path today: Gel's SQL endpoint#

Gel exposes a PostgreSQL-wire SQL endpoint. It supports queries and standard DML, but not DDL, so Gel's own schema and migration tooling remain authoritative. Use zmdb's Postgres compiler for reporting, analytics and SQL operations the endpoint supports:

import { createQueryCompiler, trustedTable } from '@zmdb/sql';
import { postgres } from '@zmdb/postgres';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.GEL_DSN });
const compiler = createQueryCompiler(postgres);
const query = compiler.selectFrom(trustedTable('users')).select(['id', 'name']).compile();

const result = await pool.query({
  text: query.text,
  values: [...query.parameters],
});

Write the zmdb declarations to match the relations and tables Gel exposes through SQL, and validate the returned rows. This is SQL against a Gel-controlled schema; it is not a native EdgeQL target, and zmdb does not create or migrate that schema.

Native target method matrix#

This table records only whether EdgeQL could express each repository operation if the schema-ownership conflict were resolved. No native Gel method is implemented or supported.

Repository methodEdgeQL query feasibility
findByIdexpressible
findOneexpressible
findexpressible
findAllexpressible
listneeds translation: the current flat predicate plan borrows SQL operator precedence
findByFullTextnot assessed: full-text search is declared in Gel's schema, the refused half
findJoinedexpressible through links and nested shapes
aggregaterefused: the callback form gives application code a SQL aggregate builder
findAllWithManyexpressible as one nested shape
createexpressible
upsertexpressible with unless conflict on
updateexpressible
deleteexpressible
withTransactionnot assessed after the target was refused on schema ownership

The query half is a better fit than this refusal might suggest. The target stops before implementation because the epic explicitly required Gel's schema-definition model too.

Why the native target was refused#

SQL Server, CockroachDB, and SingleStore demonstrate that SQL differences can be represented by traits and explicit DDL branches.

Gel is not another SQL dialect. EdgeQL uses objects, links, sets and shapes:

That last point is decisive. zmdb's design makes a TypeScript table declaration the source from which database shape, DTOs, validators and API documents are derived. Gel owns its schema and generates a client from it. Both designs cannot be authoritative for the same application.

There are only two ways to cross that boundary:

  1. zmdb generates Gel schema files and turns its migration command into input for a second migration system; or
  2. zmdb reads Gel's schema and derives TypeScript from it, making Gel the source and zmdb a client.

The second design is clearer about ownership, but it is a different product from zmdb. Neither direction is planned here.

Where EdgeQL would fit better#

Refusal is not a claim that EdgeQL is less expressive. It improves several areas where the SQL repository has to assemble results:

Repository concernSQL path todayEdgeQL query half
to-many populatesecond query, chunked IN, grouped in JavaScriptone nested shape
Populated<T, K>assembled by attachRelationsthe shape is the result
findJoinedflat rows; same-named columns on both sides collidelinks and nested objects
predicate groupingthe flat plan borrows SQL's AND-before-OR precedenceexplicit grouping
many-to-many relationshipsthe SQL path refuses a relation whose junction cannot be inferreda native multi link

Set semantics are the harder part: EdgeQL has no SQL NULL, so isNull, notNull and nullable predicate behavior need a semantic decision rather than a spelling change. The current flat predicate plan also has to become a nested tree before any non-SQL target can preserve its meaning.

Using zmdb alongside Gel#

Gel can own persistence while zmdb owns the HTTP boundary and validation:

@Controller('/users')
export class UsersController {
  @Post('/')
  async create(ctx: Ctx<Record<never, string>, unknown>) {
    const dto = assert<CreateUserRequest>(ctx.body);
    return gelClient.query(`insert User { name := <str>$name }`, dto);
  }
}

This keeps @zmdb/web, AOT validation, generated fixtures and OpenAPI for the request and response types. CreateUserRequest is hand-written rather than derived from Gel's schema, so it can drift. A round-trip test using random<CreateUserRequest>() is the cheapest guard, not proof that the two schema systems share a source.

No native compiler target, repository driver or migration integration is planned.

---

See also: Query Compiler · Architecture · MongoDB