Release Notes
Quark is v1.7.0 on the stable v1.x line. v1.x keeps API compatibility;
breaking changes go to v2.x.
Every release since v0.10.0 — including all of v1.x — is a drop-in upgrade:
no breaking changes. The last breaking change was in v0.9.0, and its
migration guide
is still the one to follow if you are coming from v0.8 or earlier.
Full commit-level detail lives in the CHANGELOG and on GitHub Releases.
v1.7.0
A minor release about telling one database failure from another — and about two places where Quark could not, on engines its own documentation recommends.
Added
-
quark.IsUniqueViolationandquark.IsDeadlock. A handler that writes to the database needs to know whether a rejected insert was a duplicate it can explain to the user, or something it should not have swallowed. Until now the only exported signal wasErrConstraintViolation, which lumps unique, foreign-key, not-null and check violations into one value — enough to know the write was refused, not enough to answer with a409and name the field. The alternative was importing your driver and matching its error type by hand, which stops working the moment the application runs on a second engine.Both predicates match on the error code the driver reports, never on message text, so they are unaffected by the server's language and by wording changes between driver releases. Both walk the wrapping chain. See Errors for when to reach for each.
Fixed
-
PostgreSQL failures went unrecognised under
lib/pq. Quark classifies driver errors to decide three things on its own: whether to retry a transaction the engine chose as a deadlock victim, whether a duplicate link row can be ignored, and whether a read should fail over off an unreachable replica. That classification matched only the error type ofpgx, while the installation guide prescribeslib/pqand the dialect accepts its driver names.On
lib/pq, none of the three recognised anything — soWithDeadlockRetrynever retried, and a downed read replica was never detected as one. Neither reported an error: the options were accepted, appeared active and did nothing. All three now read the code through the method both drivers expose, so either driver behaves the same. -
On SQL Server, a rejected insert reported the wrong error entirely.
Createsends theINSERTand the identity lookup as a single batch, and when the server rejects the insert it still answers the lookup — withNULL. Reading that into a plain integer failed, and that conversion error was returned in place of the driver's, so a duplicate key arrived as a scan error naming no constraint, no table and no column. No rejected insert on SQL Server was diagnosable, by a person or by a program. The engine's own error now reaches the caller, andErrConstraintViolationwraps it as it already did on the other engines.
v1.6.1
A patch release about the preflight that shipped in v1.6.0 believing more about your database than it had actually checked.
Fixed
-
The row-level security preflight now reads the policy, not just its name.
VerifyRLSPoliciesasked PostgreSQL whether a policy called<table>_tenant_isolationexisted and stopped there. A policy carrying that name withUSING (true)— right name, no predicate — earned a green light while every tenant read every row. That is worse than no check at all: a green check is what stops an operator from looking.It now reads the policy's
USINGandWITH CHECKexpressions and demands the two things that make them isolate: a reference to your tenant column, and a read of the session variable the router sets. It also catches a policy narrowed to one command by a laterALTER POLICY, and one that leaves the write path open. The error says which of those is wrong, not merely that something is.Scope, so the guarantee is not overread: it verifies the policy this package installs. A deployment that isolates through several hand-written policies is reported as a deviation rather than guessed at.
-
The preflight runs with the client the documentation prescribes . It used
RawQuery, which is off by default, soquark.New("pgx", dsn)— the exact clientquarktenant's own docs show — could not run it, and a boot guardrail failed indistinguishably from a real outage. It now reads the catalog directly, the way the install path already did. The doubledquarktenant: quarktenant:prefix is gone too. -
verify-rls-policiesstops accepting flags it ignores.--tenant-coland--native-rls-varnow genuinely change the verdict, because the predicate check needs them: verifying with the default column against an installation made with another one used to return OK without having checked what you believed. Flags that cannot change a read-only verdict (--dry-run,--cast,--lock-name,--lock-timeout) are refused for this action instead of being silently dropped.
v1.6.0
A minor release about the sharpest edge in multi-tenancy, plus the test kit the ORM never shipped.
Added
quarktenant.VerifyRLSPolicies— a preflight for native row-level security. ConfigureRowLevelSecurityNativeand forget the policy DDL, and Quark emits no tenant predicate at all: every tenant reads every row, with no error anywhere. Nothing checked for it. The new call verifies, per registered model, that row security is enabled, thatFORCEis set (unless you opted out — without it the table owner bypasses the policy, and the application role usually is the owner), and that the policy exists. Call it at startup and fail the boot, or gate a deploy with theverify-rls-policiesaction of your tenant runner, which exits1when a table is unenforced — distinct from2for operational errors.quarktest— the test kit.SQLite(tb)opens a client on a temporary file (not:memory:, where every pooled connection would get its own empty database),Migrate(tb, client, models…)brings the schema up in one line and surfaces tag typos immediately, andTx(tb, client, fn)runs a test inside a transaction that always rolls back. A new testing guide walks through it, including when a real engine is still required.
Fixed
quark migrate upapplied only the first pending migration. Without--steps,upanddownshared one flag variable, soupinheriteddown's default of 1: three pending migrations in a pipeline, one applied, exit code 0.--dry-runpreviewed a single migration for the same reason.- The documentation site did not build. The version marker was written as
an HTML comment in an MDX page, which aborts compilation. It now uses MDX
comment syntax, and a lint rule rejects HTML comments in
.mdx.
Changed
- The documentation archive resumes. The site keeps a snapshot per published minor so readers pinned to an older release get the matching docs; that archive had frozen at 1.2.2. It resumes with this release, and a check now fails when a minor ships without its snapshot.
- Editorial pass over the published documentation — shorter sentences, the conclusion first, and no wording that assumes knowledge of the project's internal history.
v1.5.2
A CLI correctness patch.
Fixed
- A bare
quark migrate upapplies every pending migration again. The--stepsflags ofmigrate up(default 0 = all pending) andmigrate down(default 1) were registered on the same package variable, and pflag writes each default into the bound variable at registration time — so after startup the shared variable held1, andmigrate upwithout--stepsapplied only the FIRST pending migration and exited 0. With three pending migrations in a CI pipeline, one was applied and the job went green. The flags now use separate variables, and a regression test drives the real command line with two pending migrations (every earlier test used exactly one, where the truncation is invisible).--dry-runwas affected the same way and is covered by the same fix.
v1.5.1
A durability patch under native row-level security.
Fixed
- A write under
RowLevelSecurityNativeis durable when the call returns.Create(and every single-row operation) runsINSERT … RETURNINGinside an implicit transaction whose commit used to run in a background goroutine when the operation's context ended — with no ordering guarantee against the call returning. A handler could answer 2xx while the row was not yet committed, and an immediate reader on another connection missed it (surfaced as a rare flake by an external end-to-end suite; v1.3.1 fixed a first variant of the same class). The executor now materializes the returned row, commits synchronously, and only then returns: writes are durable on return, commit failures surface as the call's own error, and single-row reads release their pooled connection immediately. The deferred commit remains only on the multi-row read path, where it affects connection release, never durability — theDeferredCommitFailurescounter now covers exactly that path. The native-RLS guide's write-semantics section is rewritten accordingly.
v1.5.0
The DX minor: everything the 2026-08-16 DX audit demanded from quark. No API breaks; every change is additive or turns a silent failure into a loud one.
Added
quark migrate create <name> --from-models <dir> --dialect <d>renders domain DDL from your model structs through the SAME type mapping the runtime migrator uses: dialect-correct column types, topological table order, foreign keys, indexes, and a Down that drops in reverse dependency order.- Rich field vocabulary in
model generate:nullable<T>,array<T>,json<T>, andbelongs_to<Model>(emits the FK column plus therel:/join:pair). - Automatic timestamps:
Createfills zerocreated_at/updated_atandUpdaterefreshesupdated_at— the 18 hand-written timestamp hooks the audit counted are no longer needed. quark initwrites the embedded runner (cmd/<app>/main.gowith the blank imports andcommands.Main()) instead of only prescribing it.Example:blocks on 19 subcommands.
Fixed
- Struct-tag typos fail fast.
quark:"notnull",db:"price,lenght=10",column:"..."and friends die inRegisterModel/Migratewith a hint (ErrInvalidTag) instead of silently dropping constraints;pk:"True"is accepted case-insensitively. - Models without a primary key get an actionable error naming the model
and the fix, instead of
sql: no rows in result setdownstream. quark.Newrejects invalid options (a string, a number, an uncalled constructor) naming each one, and an unknown driver withoutWithDialectis now an error instead of a WARN plus a silent PostgreSQL fallback.- Static builds: the mattn/go-sqlite3 error classifier sits behind a
cgobuild tag, soCGO_ENABLED=0and cross-compiles build again (new static-build CI lane). - The CLI builds its
LimitsfromDefaultLimits(), killing the partial-literalSafeMigrationsWARN on every invocation.
v1.4.1
A patch release: the CLI keeps its promises. Three defects surfaced by an external coverage exercise over the public module surface, plus a round of CLI papercuts. Two deliberate behaviour changes, no API breaks.
Fixed
model generate --fieldsoutput compiles and declares its primary key. Atime.Timeorjson.RawMessagefield now emits its import block,idgetspk:"true"(the tag the ORM parses), and the template renders the computedquark:"..."tag — only with vocabulary the ORM understands (not_nullon the from-table path).- The embed recipe no longer swallows errors. New entry point
commands.Main()(execute, print to stderr, exit non-zero) is what every recipe now prescribes; a runner built from the old printed recipe (func main() { commands.Execute() }) exited 0 in silence on any failure.commands.Execute()keeps its signature and behaviour. tenant provisioncompletes underschema_per_tenant. It creates the schema and thequark_tenantsrow and explicitly skips the migration step (which needs aTenantRouterin your own binary); an id already registered is rejected with a clear "already provisioned" error before any DDL runs, so retries never crash on duplicateCREATE SCHEMA.- Papercuts.
migrate status/versionwork on a fresh database (zero applied, no missing-table error) andstatuslists pending migrations;seed run/seed listhonour registration order;quark initfillsproject.module/project.namefrom the directory'sgo.mod; theWithdocs example uses the realJoin(table).On(...)signature.
Changed
quarktenant.InstallRLSPoliciesis now re-runnable: the rendered DDL drops the deterministic policy (DROP POLICY IF EXISTS) before recreating it, in the same transaction — re-running converges instead of failing with SQLSTATE 42710. Gate the call yourself if you relied on that failure.- Go toolchain to go1.26.6 (closes GO-2026-6090, GO-2026-6088, GO-2026-5972 in the standard library).
Narrative notes:
docs/RELEASE_NOTES_v1.4.1.md.
v1.4.0
A feature minor for safer reads and clearer limits. Nothing is a breaking change; every new behaviour is opt-in or a strictly-more-honest default.
Added
- Strict reads.
WithStrictReads(mode)flags unboundedIter()/Cursor()calls —StrictReadsWarnlogs them,StrictReadsRejectreturnsErrInvalidQuery— and, when tracking is enabled on a context, detects the N+1 access pattern and points at the missingPreload.AllowUnbounded()on the builder is the explicit escape hatch for exports and back-fills. - Blocked-cleanup visibility.
Client.BlockedPanicCleanups()counts native-RLS panic-path cleanups that a stuckdatabase/sqlnever let finish, so a leaked transaction/connection pair is observable instead of only inferable from pool exhaustion — the counterpart toDeferredCommitFailures().
Fixed
WithLimitsfills zero numeric fields fromDefaultLimits(). A partial literal likeLimits{MaxResults: 500}no longer leavesQueryTimeoutat zero (which made every query fail with an already-expired context). Booleans are not normalized — a partial literal that leavesSafeMigrationsfalse now emits one structured WARN pointing atDefaultLimits().- Security:
golang.org/x/textbumped to v0.39.0 (GO-2026-5970, reachable throughdatabase/sql).
Upgrade notes
Drop-in. If you built a Limits literal by hand and relied on the old
all-zero behaviour, start from DefaultLimits() and override — the WARN will
tell you if you didn't.
v1.3.3
A correctness patch for the native row-level-security strategy. Upgrade if
you use RowLevelSecurityNative — especially if you call Create or
Update from long-lived contexts (batch jobs, CLIs, workers).
Fixed
CreateandUpdateno longer hold their implicit transaction until your context ends. Both now scope the transaction to the operation, like every other native-RLS entry point already did. Previously, each write from a long-lived context kept one pooled connection idle in transaction (with its locks) until that context finished — enough writes could exhaust the pool, and any later DDL on the touched table (a migration, aDROP TABLEin tests) blocked behind those locks. As part of the same fix, a row you just wrote is visible to reads on the same context (read-your-writes), which previously only held across contexts.- The executor no longer leaks its pooled connection if the driver panics. A panic inside a query or transaction begin used to abandon the connection; the pool now gets it back on every path, including panics the standard library survives.
- Driver and transaction errors are no longer masked as
ErrNoRows. On theQueryRowpath, a failure to begin the transaction or set the tenant context used to surface as a generic "no rows" result; the real error now reaches yourScancall intact, wrapped with the stage that failed, soerrors.Is/errors.Aswork.
Upgrade notes
Nothing to do. If you had sized pools defensively or added per-operation contexts to work around hangs during migrations, those workarounds are no longer needed — see the troubleshooting entry in the native row-level security guide if a DDL statement still hangs on releases before this one.
v1.3.2
A robustness patch for the native row-level-security strategy. Upgrade if you
use RowLevelSecurityNative, especially with connection pools sized close to
your concurrency.
Fixed
- Connection acquisition under native RLS honours your context. Waiting
for a pooled connection used to be uncancellable: with a saturated pool, a
request could keep waiting past its own deadline. Acquisition now aborts
with the caller's
ctx.Err(); only the transaction's lifecycle stays detached from cancellation (the v1.3.1 write-durability behaviour is unchanged, and its semantics are now spelled out in the native row-level security guide's Limitations section). - A failed deferred commit is loud. If the deferred commit behind a
native-RLS write fails, the failure is logged at error level even without a
configured logger, and counted —
Client.DeferredCommitFailures()exposes the count so operators can alert on it. Batch inserts are covered by the same regression tests as row-by-row writes.
Upgrade notes
Nothing to do. Behaviour changes only affect failure paths that previously hung or went silent.
v1.3.1
A correctness patch. Upgrade if you chain different set operators in one query, or if you use the native row-level-security strategy with writes.
Fixed
- Mixed set-operator chains are rejected honestly.
A.Union(B).Intersect(C)used to emit flat SQL whose meaning depends on the engine (SQLite and Oracle evaluate left-to-right; PostgreSQL, SQL Server, MySQL and MariaDB giveINTERSECTprecedence) — the same query silently returned different rows on different engines. Chains that mix operators now returnErrUnsupportedFeature; materialize intermediate results in steps instead. Chaining the same operator,UNIONwithUNION ALLincluded, keeps working. - Native row-level security no longer loses writes. Under the
RowLevelSecurityNativestrategy, anINSERT … RETURNINGran inside an implicit transaction whose lifecycle was tied to the caller's context — and cancelling that context (which is how every request context ends) made the database roll the insert back instead of committing it. The transaction's lifecycle is now detached from the request context, so the deferred commit is deterministic. This changes cancellation semantics on the query paths: each statement still honours the context while it runs, but once a statement has executed, cancelling the request no longer reverts the write — the implicit transaction commits when the context ends, after control has returned to the caller (the exec path is unchanged: it commits synchronously before returning). See PostgreSQL Native RLS → Limitations for the full write-semantics contract.
Upgrade notes
If you relied on mixed set-operator chains, split them into explicit steps — the previous behaviour differed per engine, so results were only ever trustworthy on one of them.
v1.3.0
A drop-in minor. It rounds out the set-operator surface and makes its per-engine behaviour honest.
New
INTERSECT ALLandEXCEPT ALL.IntersectAllandExceptAllare the multiset variants of the existingIntersect/Except: they keep duplicate rows instead of collapsing them. Available on PostgreSQL and MariaDB.
Fixed
- Set operators now report unsupported engines cleanly. SQL Server and
SQLite have
INTERSECT/EXCEPTbut noALLvariants, and Oracle only gainedINTERSECT ALL/MINUS ALLin 21c — a version Quark does not assume without a runtime probe; asking for anALLvariant on any of them now returnsErrUnsupportedFeatureinstead of emitting SQL the engine rejects with a confusing parser error. The documentation forIntersect/Exceptalso no longer claims MariaDB is unsupported — it has been supported since 10.3.
Upgrade notes
Nothing to do. Everything above is additive; existing code behaves exactly as before.
v1.2.2
A correctness and security patch. Upgrade if you provision tenants from the CLI, paginate compound selects, or batch upserts.
Security
quark tenant provisionbuilt SQL by string concatenation. The tenant id and strategy were interpolated straight intoCREATE DATABASE/CREATE SCHEMA/INSERT. The id is now validated against the TenantRouter contract before any SQL runs, DDL identifiers are dialect-quoted, and the registryINSERTis parameterised. If you provision tenants from names you do not fully control, upgrade.
Fixed
Count()andPaginatewere wrong on compound selects. They counted only the first operand of aUNION/INTERSECT/EXCEPT— a query whoseList()returned 4 rows could reportCount() == 2. The count is now wrapped asSELECT COUNT(*) FROM (<compound>).Upsert/UpsertBatchwith no conflict columns now returnErrInvalidQueryon every engine, instead of panicking on MySQL/MariaDB and behaving differently elsewhere.OffsetwithoutLimitnow renders the correct per-engine form, instead of being silently dropped (MySQL) or producing invalid SQL (SQLite, MariaDB).- SQL Server reads generated keys back from an upsert via
MERGE … OUTPUT INSERTED.<pk>on both branches. Oracle'sMERGEhas noRETURNING, so the primary key stays zero there — a documented limitation, not a bug. - CLI commands now report reality.
migrate up/downandseed runexit non-zero on an empty registry and print how to embed your models;validateloads your Go structs and compares columns in both directions;tenant migrateresolves the tenant's own DSN fromtenant.dsn_templateinstead of migrating the default database;inspect tableandmodel generateexit non-zero when the table does not exist;init --dialect bogusfails before writing any files. Flags that never did anything (--skip-seed,--tenant-id,--env) were removed.
New
UpsertBatchchunks likeCreateBatch, and both now size their chunks against per-dialect bind-parameter ceilings (~65k on PostgreSQL/MySQL/MariaDB, ~32k on SQLite, ~2100 on SQL Server) instead of a single universal limit of 2000.INTERSECTandEXCEPTon MariaDB (10.3+). MySQL still returnsErrUnsupportedFeature: it gained them in 8.0.31, and Quark cannot assume that version without probing.
Upgrade notes
No breaking changes. The --skip-seed, --tenant-id and --env CLI flags were
removed; they were accepted and ignored, so any script passing them was already
not getting the behaviour it asked for, but a script that passes them will now
fail rather than silently do nothing.
v1.2.1
A CLI, docs and raw-query-guard patch. No query-builder or CRUD changes.
Fixed
- The CLI could not connect to PostgreSQL at all.
quark initwrotedriver: postgresql, but the registered driver ispgx, so every database command failed withunknown driver; the Oracle driver was not even linked into the binary. The CLI now maps dialect names to registered drivers and links go-ora. - Failures exit non-zero.
migrate,inspect,tenant,seedandinitprinted their error and exited 0 — which meant a failing migration passed a CI step. If you were relying on the exit code, this release is the one that makes it trustworthy. syncno longer pretends. Its--dry-run,--safe,--no-transactionand--modelsflags did nothing and are gone. The command now checks the connection and prints how to wireclient.Sync(...), which is what actually performs a schema sync (it needs your compiled model types).- The raw-query guard stopped rejecting valid SQL:
--inside a single-quoted string literal ('range--max') is allowed again, while--outside one is still rejected.
New
inspect --format json|yaml, andquark version/--version.
Security
- Dialect
Quote()now doubles embedded closing quotes — defence in depth underneath the existing identifier allowlist.
Upgrade notes
No breaking changes. Expect previously-green CI steps to start failing honestly if they were running migrations that did not actually apply.
v1.2.0
Scaling features that were deferred at v1.1.
New
- Cross-instance cache-stampede protection. Opt in with
WithCacheCrossInstance(), backed by the optionalCacheLockercapability (implemented by thememoryandredisstores). One instance wins the lock and recomputes; the others wait and re-read, so a hot key expiring does not send every process in your fleet to the database at once. If the lock backend fails, it degrades to the previous in-process behaviour rather than erroring. - Shard key from the entity. Entities that implement
ShardKeyercan route writes withWithShardKeyOf(ctx, entity)instead of restating the key at the call site. - Cross-shard reads.
ScatterGatherandScatterCountrun a query on every shard concurrently and merge the results, with an explicitScatterMergeyou supply.COUNTis the only aggregate merged for you.
Fixed
- The
Updatezero-value warning no longer fires on nil pointers. - Faster reads: the scan plan is memoised and result slices are pre-sized.
Security
- Toolchain pinned to go1.26.5 and pgx to v5.9.2, picking up the upstream security fixes released since the v1.1.5 pins.
Upgrade notes
No breaking changes. Everything above is opt-in.
v1.1.5
Fixed
SQLGuarderrors are now matchable. A rejectedWhereoperator, a raw query missing placeholders, or a raw query matching a suspicious pattern now wrapquark.ErrInvalidQuery, soerrors.Is(err, quark.ErrInvalidQuery)catches them. Previously they returned a message the sentinel did not wrap, so error handling written against the sentinel silently missed them. No SQL or behaviour change beyond the error value.
This release also lands a documentation-versus-code audit: the published docs
for query-builder joins, the Dialect interface, SQLGuard's lexical validation,
the CLI subcommands, PostgreSQL's CreateListener, batch hooks and the
composite-primary-key cache tag were corrected to match the shipped API.
Upgrade notes
No breaking changes.
v1.1.4
Five cross-engine correctness fixes.
Fixed
CreateBatchback-fills generated primary keys on MySQL and SQL Server. Those engines cannot read keys back from a multi-row insert, so when the primary key is auto-generated Quark now inserts per row and recovers each key viaLastInsertId/SCOPE_IDENTITY.entity.IDis populated on every engine instead of being silently left at 0.- Recursive CTEs work on Oracle and SQL Server.
WithRecursiveno longer emits theRECURSIVEkeyword there (both engines infer recursion structurally and reject the keyword), which previously failed with ORA-02000 or a T-SQL syntax error. - Paginated set operations work on Oracle and SQL Server. A
Union/Intersect/ExceptwithLimit/Offsetand no explicitOrderBynow emits a positionalORDER BY 1instead of ordering by the primary key, which those engines rejected under a compound select. - Batch and upsert lifecycle hooks fire.
CreateBatch/UpdateBatchrunBeforeCreate/BeforeUpdateper entity, andUpsert/UpsertBatchrunBeforeCreate, so timestamp, default and derived-field hooks apply to batched and upserted rows.After*hooks remain single-write only.
Upgrade notes
No breaking changes. If you relied on Before* hooks not running on batch
paths, note that they now do.
v1.1.3
Fixed
CreateBatchback-fills the generated primary key on Oracle, viaRETURNING … INTO, instead of leavingentity.IDat 0.- MariaDB JSON columns no longer drift in the schema diff. The introspector
recognises a
longtextcarrying MariaDB's auto-addedjson_valid(col)check asjson, so the diff stops proposing a cosmeticlongtext → JSONalter. - Cache invalidation after a batch insert.
CreateBatchnow invalidates the table tag on PostgreSQL, SQLite and MariaDB, so a cached table-level read no longer serves stale rows after a batch insert.
Upgrade notes
No breaking changes.
v1.1.2
Fixed
ApplyPlancreates a real primary key. Creating a table from a plan now renders the primary key — auto-increment for a single integer key, a table-level constraint for composite keys — so a plan-created table is interchangeable with one created byMigrate.Columngains aPrimaryKeyfield, populated by introspection on all six engines and compared by the diff. A primary-key change is reported but refused by the executor: applying it needs a table rebuild.PlanMigrationreturns an empty plan on a freshly migrated database, as documented. Many-to-many join tables are now part of the desired schema, so the diff no longer proposes a destructiveDROPof a table Quark itself created; and catalog-decorated defaults ('x'::text,((1)), unquoted MySQL strings, bool-literal case) and type aliases (timestamp without time zone,tinyint(1), OracleTIMESTAMP(6)) no longer produce cosmetic alter operations.
Upgrade notes
No breaking changes.
v1.1.1
Fixed
- Cache invalidation after an insert.
Createnow invalidates the model's table tag on every insert path — PostgreSQL/SQLite/MariaDB (RETURNING) and SQL Server (OUTPUT/SCOPE_IDENTITY) — so a cached table-level read no longer serves stale results after an insert. - Boolean column defaults migrate everywhere.
default:"1"on aboolfield now migrates on all six engines (PostgreSQL getsTRUE/FALSE). ErrInvalidIdentifieris reachable viaerrors.Ison every validation path.quark model generate --fieldscreates its--outdirectory and reports failures with a non-zero exit code.
Upgrade notes
No breaking changes.
v1.1.0
A hardening release: a systematic cross-engine pass over the whole public
surface, plus the correctness fixes it surfaced — versioned migrations on SQL
Server, a MariaDB schema-diff false positive, CreateBatch bind-parameter
chunking, dialect-aware savepoints, multi-tenant write routing, and three
eager-loading fixes. No new public API.
Upgrade notes
No breaking changes.
v1.0.0
The first stable release, and the commitment to keep the v0.13 public surface
compatible for the whole v1.x line. Oracle joined the blocking CI matrix, which
means all six dialects — PostgreSQL, MySQL, MariaDB, SQLite, SQL Server and
Oracle — are now covered by tests that must pass before anything ships.
The benchmark suite gained a code-generation tier (ent and sqlc), which confirmed that Quark's own code generation is a type-safety feature and not a speedup. We had originally aimed for a multiple-times speedup from codegen; the measurements did not support it, so we stopped claiming it. See Benchmarks.
Upgrade notes
No new public API beyond v0.13, and no breaking changes since v0.9.0.
v0.13.0
New
- Read replicas.
WithReplicas(dsns...)opens read-only pools and spreads reads across them 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 connection error on a replica fails over to the primary, and that replica drops out of rotation for a cooldown (WithReplicaDownCooldown, default 5s). Reads insideClient.Txand underRowLevelSecurityNativealways use the primary. - A runnable stress and load harness (latency percentiles, throughput, pool
contention) under
benchmarks/stress.
Fixed
- Lower per-query allocation: the copy-on-write query-builder clone now shares slices and appends within bounded capacity.
Upgrade notes
No breaking changes. Replicas are opt-in.
v0.12.0
New
- Compile-time column type-safety, on top of the code generator.
quark genemits a<Model>Columnsvalue of typed column handles per model, and the query builder gainsQuery.WhereP—WHEREconditions with no magic column strings, where both the column and the bound value are checked at compile time. It is pure compile-time sugar: the stringWhere(...)API stays valid and the two are interchangeable.
Fixed
- The audit row diff is only built when an audit sink is configured (~9% fewer allocations on insert).
Upgrade notes
No breaking changes.
v0.11.0
New
- Opt-in code generation.
quark genparses your model package and emits aquark_gen.goper package, registering typed scanners on the read path and a typedINSERTbinder for single-integer-primary-key models. Reflection remains the permanent default; codegen is fully opt-in and falls back to reflection for anything it does not cover. - A reproducible benchmark module (Quark vs hand-written
database/sqlvs GORM).
Profiling against that baseline measured what codegen actually buys: ~2–5% on
scan and ~1% on insert. Quark's per-operation CPU is dominated by
database/sql and the database engine, not by reflection. Generate for
correctness and forward compatibility, not for speed.
Upgrade notes
No breaking changes.
v0.10.0
Fixed
JSON[T]andArray[T]round-trip on SQL Server. A[]byte→VARBINARY→NVARCHAR conversion was corrupting them.- Rolling back to a savepoint discards the hooks queued in that scope.
After*,OnCommitandOnRollbackcallbacks registered inside a rolled-back savepoint no longer fire their side-effects on the outer commit. - Raw SQL under
RowLevelSecurityNativeemits aquark.tenant.raw_under_native_rlswarning.
Upgrade notes
No breaking changes. WithDeadlockRetry is now backed by a real cross-engine
deadlock-retry integration test (PostgreSQL, MySQL, MariaDB).
v0.9.0
New
- PostgreSQL engine-enforced multi-tenancy (
RowLevelSecurityNativeplus thequarktenantpolicy CLI). - Transactional
After*hooks that fire post-commit, plusBeforeFindandAfterFind. Tx.OnCommit/Tx.OnRollbackandquark.TxFromContext.- An
EventBus(Client.UseEventBus) and an optional audit log (Client.EnableAuditLog) written atomically with each write.
Upgrade notes
Two breaking changes. Follow the v0.9.0 migration guide.
v0.4.0 – v0.8.0
Composable query builder (typed AST, subqueries, CTEs, window functions, set
operators, pessimistic locking); schema-as-code migrations (introspection,
pure-Go diff, transactional and resumable apply, the quarkmigrate CLI,
backfill); observability and L2 cache (OpenTelemetry metrics, span redaction,
slow-query log, stampede protection, deadlock retry); per-column timezones and
Array[T].
v0.3.0
The first proper tag since v0.1.1. Bundles the early security and correctness fixes with rich types, dirty tracking, optimistic locking and soft-delete scopes.
v0.1.0
Public release baseline.
Core
- Generic
Query[T]entry point throughquark.For[T](ctx, provider). - Immutable builder methods for predictable query composition.
- CRUD helpers for create, update, delete, hard delete, upsert, and map updates.
- Read helpers for
Find,First,List,Count, aggregates, pagination, streaming, and cursors. - Validation through
validatetags and model-levelValidate(context.Context) error. - Lifecycle hooks for create, update, and delete paths.
Dialects
- SQLite, PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle dialects.
- Dialect-specific placeholders, identifier quoting, pagination, upsert SQL, generated-ID handling, JSON extraction, and DDL helpers.
- Custom dialect registration through
RegisterDialect.
Schema
Migratefor table and many-to-many join-table creation.Syncfor additive changes, renames, and controlled destructive drops.CreateIndexandAddForeignKeyhelpers.- Versioned Go migrations through
github.com/jcsvwinston/quark/migrate.
Relations
Preloadforhas_one,has_many,belongs_to, many-to-many, and polymorphic relations.- Recursive association persistence for common aggregate saves.
- Tenant context propagation through association loads and saves where tenant columns are present.
Production Features
- Transactions, manual transactions, savepoints, and nested savepoint-style callbacks.
TenantRouterwith database-per-tenant, schema-per-tenant, and row-level strategies.- Cache abstraction with memory and Redis stores.
- Query observers, middleware, and OpenTelemetry middleware.
- SQLGuard validation for identifiers, operators, and raw-query escape hatches.
Compatibility Notes
- The
cmd/quarkCLI ships and is installable viago install— it provides migration, tenant, andquark gencodegen subcommands (Operational Workflows). DeleteByandDeleteBatchperform hard deletes.Selectaccepts simple identifiers rather than arbitrary SQL expressions.- Raw SQL APIs require
AllowRawQueries: true. - Migration commands that use
migrate.Migratorshould use a client configured withAllowRawQueries: true.