Roadmap
Quark is on the stable v1.x line — the current version is the one at the top
of the release notes. This page is here so you can tell,
at a glance, what you can build on today, where the sharp edges are, and what is
coming — without having to guess which parts of the documentation are a promise
and which are a fact.
Everything under "Shipping today" is in main and covered by the cross-engine
test suite. Everything under "Planned" is not built yet.
Shipping today
Type-safe builder and composable query AST
- Type-safe
Query[T]builders with immutable composition (clone per method). - Typed expression AST (
Col/Lit/Func/And/Or/Not/In/Exists). - Subqueries composable through
AsSubquery. - CTEs (
With,WithRecursive). - Window functions (
OVER (PARTITION BY … ORDER BY …),RowNumber/Rank/Lag). - Set operators:
UNION,INTERSECT,EXCEPT. - Pessimistic locking:
ForUpdate,ForShare,SkipLocked,NoWait. - Optimistic locking:
quark:"version"tag plusErrStaleEntity. HavingAggregatefor HAVING over aggregates.- Nested
Preloadwith dotted paths ("Orders.Items.Product"). IN(...)chunking that respects dialect limits (Oracle 1000, SQL Server 2100).- Structured
JoinBuilderwithValidateJoinOn.
Six dialects and SQLGuard
- SQLite, PostgreSQL, MySQL, MariaDB, SQL Server and Oracle — all six run in the blocking test matrix on every change, so cross-engine support is a tested property rather than a claim.
- SQLGuard validation for identifiers, operators, raw queries, JSON paths and JOIN-ON clauses.
- Dialect-specific upserts (
ON CONFLICT/ON DUPLICATE KEY/MERGE). - Custom dialects via
RegisterDialect.
Rich types
Nullable[T]generic.JSON[T]typed JSON column wrapper.Array[T]typed array wrapper (native on PostgreSQL, JSON-backed elsewhere).RegisterTypeMapperfor extensible mapping (decimal, UUID and similar, opt-in).time.Durationmapped out of the box.- Per-column timezones via the
quark:"tz=..."tag or the client-wideWithDefaultTZ. The wire contract is always UTC.
CRUD, dirty tracking and soft delete
Create/Update/UpdateFields/Delete/HardDelete/Upsert/UpsertBatch/CreateBatch/UpdateBatch/DeleteBatch.- Dirty tracking via
Tracked[T]: snapshot at load, thenSave()emits an UPDATE over changed fields only. This is what closes the classic ORM trap whereUpdate(entity)silently skips a field you set tofalse,0or"". - Soft-delete scopes:
WithTrashed,OnlyTrashed,Restore.
Transactional lifecycle hooks
- Before/After hooks for create, update and delete, plus
BeforeFind/AfterFind. After*hooks fire post-commit underClient.Tx, so work that was rolled back does not fire its side-effects.Tx.OnCommit/Tx.OnRollbackandquark.TxFromContextfor arbitrary commit/rollback side-effects.- Rolling back to a savepoint unwinds the hooks queued in that scope.
Migrations (schema as code)
Migrate,Sync,CreateIndex,AddForeignKey.- Neutral schema introspection (
Client.IntrospectSchema) on all six engines. - Pure-Go schema diff (
Diff,PlanMigration,ApplyPlan) with round-trip identity: afterMigrate(model),PlanMigration(model)returns an empty plan on all six engines — so a clean database really does report as clean. - Transactional or resumable execution: PostgreSQL, SQL Server and SQLite run migrations in a transaction; MySQL, MariaDB and Oracle (which cannot roll back DDL) run them resumably with checkpoint bookkeeping.
- Distributed migration lock: PostgreSQL
pg_advisory_lock, MySQL/MariaDBGET_LOCK, SQL Serversp_getapplock, OracleDBMS_LOCK(needsGRANT EXECUTE ON DBMS_LOCK). SQLite returnsErrUnsupportedFeature. - Orchestrated
Backfillwith primary-key batching and resume tokens. - Per-client model registry (
Client.RegisterModeland friends). - Versioned Go migrations via
github.com/jcsvwinston/quark/migrate. quarkmigrateplan/verify/apply package, embeddable in your ownmigrations/main.go.
Multi-tenancy (four strategies)
DatabasePerTenant, with an LRU of clients.SchemaPerTenant.RowLevelSecurityClient— client-side WHERE injection, on all six engines. The tenant predicate composes correctly throughOr()groups.RowLevelSecurityNative— PostgreSQL engine-enforced row-level security viaset_config('app.tenant_id', …, true)andCREATE POLICY, so even raw SQL cannot read another tenant's rows. PostgreSQL only; other engines fail fast withErrUnsupportedFeature. The embeddablequarktenantlibrary generates the policy DDL. See Native RLS.- Tenant context propagates through association loads and saves.
L2 cache (memory and Redis)
- Pluggable
CacheStore(memory, Redis). - Stampede protection: a hot key expiring never sends a thundering herd to the
database — singleflight plus probabilistic early refresh, tunable with
WithCacheJitterandWithCacheXFetchBeta. Cross-instance coordination is opt-in withWithCacheCrossInstance(). - Per-row invalidation: a
<table>:<pk>tag on top of the table tag; mutations register the primary keys they touched. - Deterministic, type-tagged, length-prefixed cache keys.
Observability
- OpenTelemetry traces (spans carrying
db.statement/db.operation). - OpenTelemetry metrics: the
quark.queries.totalcounter and thequark.queries.duration/quark.queries.rowshistograms. WithSpanRedactionkeeps bind values out of spans by default. Including them is an explicit opt-in for local debugging.WithSlowQueryThresholdemits structured slow-query warnings.- Query observers and middleware.
Transactional resilience
Client.Tx(...)callback API, nested savepoint-style callbacks, explicit savepoints, isolation levels.WithDeadlockRetry(maxAttempts)re-runs the transaction closure when the engine picks it as a deadlock victim (PostgreSQL 40P01, MySQL 1213, SQL Server 1205, Oracle ORA-00060), with exponential backoff and jitter. Opt-in and context-aware.
Audit log and events
Client.EnableAuditLog(ctx, AuditConfig)records every create, update and delete into thequark_audittable on the same connection and transaction as the write, so the audit row cannot survive a rolled-back change.Client.UseEventBus(bus)publishescreated/updated/deletedevents synchronously after commit. Delivery is at-least-once and there is no transactional outbox — see Events for what that means for your subscribers.
Code generation (opt-in)
- The
quarkCLI, withgen,init,inspect,migrate,model,seed,sync,tenantandvalidatesubcommands. - Typed scanners on the read path (
List/First/Find) and a typed INSERT binder for single-integer-primary-key models. - Compile-time column accessors (
<Model>ColumnsplusQuery.WhereP): column typos and wrong-typed values fail at build time instead of at runtime. - A versioned generator contract with a model-hash drift check. Files generated against an incompatible version fall back to reflection by design, so stale generated code degrades performance rather than breaking your build.
Code generation is a type-safety feature, not a speedup — see Benchmarks for the measurements behind that statement.
Stored routines
- Helpers for calling stored procedures and functions.
Horizontal scaling
- Read replicas:
WithReplicas(...)routes reads to replicas while writes stay on the primary;Sticky(ctx)pins a read to the primary when you need read-your-writes. A read that hits a transient error on a replica fails over to the primary, and that replica leaves the rotation for a cooldown. See Read replicas. - Sharding (
ShardRouter): routes each query by shard key. Cross-shard reads (ScatterGather/ScatterCount) and deriving the shard key from the entity (ShardKeyerplusWithShardKeyOf) both ship. There is a runnable example underexamples/sharding/. - A stress and load harness (latency percentiles, throughput, pool contention)
under
benchmarks/stress/, and a comparison benchmark harness that includes ent and sqlc. See Benchmarks.
Known limits
These are deliberate boundaries, not bugs. Read them before you design around a capability Quark does not have.
- No cross-shard transactions or joins. A transaction is bound to one shard's
client; there is no two-phase commit. Cross-shard reads are an explicit
ScatterGather, and onlyCOUNTis merged for you. - Events are at-least-once with no transactional outbox. If the process dies between commit and publish, the write is durable but the event is lost. Make subscribers idempotent, or publish from your own outbox.
LISTEN/NOTIFYis PostgreSQL-only, both inbound and outbound; other engines returnErrDialectNotSupported. Delivery is fire-and-forget: notifications emitted while the listener connection is down are lost. It is not a queue.- Bulk and WHERE-based methods do not fire
After*hooks.CreateBatch,UpdateBatch,DeleteBatchandDeleteByskip them.Before*hooks do run per entity inCreateBatch/UpdateBatch. DeleteByandDeleteBatchhard-delete, even on a soft-delete model.SelectandWhereaccept simple identifiers, not arbitrary SQL expressions. Dotted columns and expressions need a view or controlled raw SQL.- The versioned migration registry is still global, while the model registry has been per-client since v0.6. This is known debt.
- Native row-level security requires PostgreSQL policies, and it is not a drop-in upgrade from the client-side strategy.
Planned
No dates. These are ordered roughly by how likely they are to happen, and each one is here because it solves a problem people actually hit.
Guardrails against accidental full-table work
An opt-in strict mode that flags an unbounded Iter() and detects N+1 query
patterns at runtime. The failure mode it targets is the classic one: code that is
fast on a developer's 200-row table and falls over on production's 20 million.
Design sketched; not built.
Ready-made event bus implementations
EventBus implementations for NATS, Kafka and Redis Streams, so that publishing
CRUD events to your existing infrastructure does not start with writing an
adapter. The interface and the logger/OpenTelemetry defaults ship today; these
would be batteries included.
Pluggable ID strategies
UUID v7, ULID and Snowflake identifiers as built-ins, rather than something you
wire yourself through RegisterTypeMapper.
Schema-first workflow
Generating reviewable migrations from a declarative schema, in the style of Atlas or Prisma. The pure-Go schema diff that shipped in v0.6 is the foundation; the declarative layer on top is the part that does not exist yet. This is demand-led — if the Go-structs-as-schema workflow is serving people well, it stays where it is.
More engine-native features
Additional database-specific capabilities exposed behind dialect extension points, driven by what people ask for.
Not planned: faster code generation
Extending code generation to the UPDATE, partial-update and batch binders is not on the roadmap. We measured it: the payoff is around 1%, because per-operation cost is dominated by the driver round-trip and by allocation, not by reflection. It would only be reopened for type-safety reasons, never for speed.