Repository navigation
release: v0.20.0 - #65
Merged
Merged
Conversation
…nal error comparison - AGENTS.md SQLite: mode=ro works only in a file: URI. modernc.org/sqlite and mattn/go-sqlite3 cut the query off a plain path, so the reader opens read-write and, with _txlock=immediate, waits behind the writer (database is locked). On a real mode=ro connection SQLite runs BEGIN IMMEDIATE as a read transaction. - AGENTS.md String Operators: EscapeChars() with no arguments turns escaping off. - MIGRATION_GUIDE v0.19.1: compare Transactional errors with errors.Is, not ==. - TROUBLESHOOTING: readers that hit database is locked.
A bare INDEX.md rule matched in every directory and hid docs/INDEX.md. Private dirs are ignored by directory only.
…epare, logging and AutoID lookup
Upsert and BatchInsert with OnConflict take exactly one of DoUpdate(cols...), DoUpdateAll() or DoNothing(). No action, two actions, an empty DoUpdate, a DoUpdate column that is not inserted and PostgreSQL/SQLite DoUpdate without a target are build errors; nothing is sent. Before, they ran as a plain INSERT, updated every column, or failed with a syntax error at run time. - DoUpdateAll: every inserted column except the target, on every dialect - MySQL DoNothing renders ON DUPLICATE KEY UPDATE c = c instead of no clause - WithMySQLUpsertSyntax(MySQLUpsertRowAlias) for the 8.0.19+ row alias - Model Upsert honours Exclude (insert and update) and rejects unknown names - dialects render a validated Upsert description; validation lives in core FIX-010 rows 1, 2, 4, 5, 7, 8, 9.
…ssing columns (ADR-018) Tables without TableName() are snake_case singular (UserProfile -> user_profile; was lower case + s). Untagged fields are snake_case columns (was the Go name). TableName() with a pointer receiver works on value models. Embedded structs are flattened on write as they were on read. Legacy db:"pk" takes its column from the naming rule instead of writing a column named pk. One struct description (fields, embedded paths, columns, primary key) serves write, read, PK and UpdateChanged, cached per DB. Naming collisions, derived names over 63 bytes and refused names are errors. Scanning into a struct now fails when a field has no result column (ScanRequireFields, default) unless the query lists its columns; ScanLenient and ScanStrict per DB or per query. New: WithNaming, Naming, Snake, SnakeCase, SplitAcronyms, Initialisms, GoInitialisms, TablePrefix, PluralTables, ScanMode, WithScanMode, SelectQuery.ScanMode, Query.ScanMode. WrapDB takes options (FEAT-013). Removed dead internal GetTableName, DefaultFieldMapFunc and the reflection helpers they duplicated. FIX-011, FEAT-013.
ForUpdate, ForShare and ForUpdateSkipLocked outside a transaction are build errors on every dialect: on *DB the lock ended with the statement, so two SKIP LOCKED workers could claim one row. A lock with UNION/INTERSECT/EXCEPT, GROUP BY, DISTINCT, Count() or Exists() is an error too (PostgreSQL rejects them; Count/Exists dropped the lock). SQLite is detected by dialect, not by driver name. Integration tests prove the locks on PostgreSQL and MySQL.
…rs (ADR-020)
db.Delete("t").Execute() deleted every row; it now fails before anything is
sent. The whole table needs the explicit AllRows() on UpdateQuery and
DeleteQuery (AllRows with Where is an error). A condition that renders empty
(And(), Or(), HashExp{}, Like without values, "") dropped the WHERE clause
and hit every row; it is an error in UPDATE/DELETE, through Where, AndWhere
and OrWhere. SELECT keeps the old meaning.
Model Insert(fields...), Update(fields...) and Exclude(...) skipped names that are not columns of the model; BatchInsert ValuesMap dropped keys that are not batch columns; an autoid gen= with no registered generator fell back to UUID v7. Each is now an error and nothing is sent. Un-skips the ValuesMap integration test (AUDIT-001 P2, O2).
…hout a dialect NewDB (deprecated since v0.12.0) and QueryParams were the two deprecated symbols left, against the no-deprecations-before-v1.0 rule: use Open and Params. Open with a driver name that has no SQL dialect panicked in GetDialect; it returns an error before the pool is opened, and the message no longer points at an internal package (AUDIT-001 O4).
… start, stop failed containers soSeed inserts explicit ids; PostgreSQL SERIAL does not move past them, so the next generated key collided (Tx_RollbackDiscardsEveryStructOp failed on PG). A cold MySQL 8.0 start took 63 s against a 60 s limit; a failed start left the container running when the Ryuk reaper is disabled.
testcontainers.WithWaitStrategy caps the wait at 60 s whatever the strategy's own startup timeout is, so MySQL starts under load still failed after ~72 s.
…he statement A construction error in a FromSelect subquery, a Union/UnionAll/Intersect/Except branch or a With/WithRecursive CTE was dropped with the condition that caused it, so the statement ran unfiltered (S2-S4). Count, Exists and Explain ignored even the query's own error (S5, S7). SelectQuery.err walks every embedded query, at any depth, and reports a query that contains itself instead of overflowing the stack. nil arguments to these methods panicked in the public wrappers (S6); they are now build errors, and core no longer drops a nil set-operation branch silently.
Count with GROUP BY returned the first group's row count and dropped GROUP BY expressions; it ignored DISTINCT, set operations and CTEs. Exists sent SELECT 1 with only FROM, JOIN and WHERE, so HAVING, DISTINCT, later UNION branches and CTEs were lost. Both now evaluate the rows All would return: grouped, distinct and compound queries are counted as a derived table, plain queries keep SELECT COUNT(*); Exists wraps the query in SELECT EXISTS(...). ORDER BY, LIMIT and OFFSET stay ignored so a paginated query reports its total. Reference: GORM Count returns the number of groups.
Every operand of UNION/INTERSECT/EXCEPT was parenthesized; SQLite's compound-select grammar takes no parentheses, so set operations and WITH RECURSIVE failed with a syntax error on SQLite although the godoc claimed support. ozzo-dbx renders the same parentheses (no SQLite override). On SQLite plain operands are now bare; an operand with its own ORDER BY, LIMIT, OFFSET, WITH or set operations becomes SELECT * FROM (...), keeping its rows and grouping. PostgreSQL and MySQL output is unchanged.
…es in hooks, WITH in DetectOperation P1: UnpinQuery returned true for a cached query that was not pinned. P3: IsHealthy and Stats().Healthy stayed true after Close. P4: a second Close with WithHealthCheck panicked (close of closed channel); Close is idempotent through a closed flag shared by WithContext copies. P6: statements PostgreSQL/MySQL rejected at prepare time returned before the QueryHook, so WithLogger never logged them; prepareStatement reports them. P5: DetectOperation called every WITH statement a SELECT; it now skips the CTE list and classifies the statement after it, and treats a compound select starting with a parenthesis as SELECT.
UpdateStruct wrote every column, the key included: the godoc example (a struct without an ID plus Where on id) ran SET id = 0, and a Where on another column changed that row's key to 0. Like Model().Update(), it now skips the primary-key columns (pk tag or the ID/Id fallback); a struct with nothing but its key is an error. Godoc examples on DB, Tx and QueryBuilder fixed.
The WithRecursive examples put "1 as level" and "h.level + 1" into Select,
which quotes them as identifiers ("1", "h"."level + 1"); the SelectExpr
godoc and SUBQUERY_GUIDE passed the alias as an argument, failing with
"0 placeholders but 1 args". Examples rewritten and checked against the
builder on PostgreSQL, MySQL and SQLite; the rule is on SelectExpr and in
AGENTS.md.
The word-boundary pattern treated "_" as a letter, so password_hash, access_token, refresh_token, user_password and stripe_api_key were logged in clear. A sensitive name now matches as a word or as a part of a snake_case name; over-masking (auth_method) is the safe direction.
…_creators) MySQL 8 enables the binary log; a user without SUPER then gets error 1419 on CREATE TRIGGER, so the upsert trigger tests failed on MySQL (first run).
… WithLogger (O3) QueryHook and WithLogger promise every statement, but the raw methods of DB and Tx went straight to database/sql. They now report a QueryEvent: QueryContext when the rows are returned, QueryRowContext with row.Err().
…rrors (O5, O6) The WarmCache/PinQuery examples warmed hand-written SQL with ? that no builder query sends (the key is the exact SQL with the dialect's quoting and ) and that PostgreSQL cannot prepare. The examples now use ToSQL; a test pins that the ToSQL text is the cache key. The PRODUCTION_DEPLOYMENT health handler returned err.Error() to clients; QUERY_ANALYZER_GUIDE matched error text.
The public wrappers still said COUNT(*) ignores the select list and Exists wraps the query; they now state the rule (rows All would return; ORDER BY, LIMIT, OFFSET ignored). NotExists(nil) in DELETE/UPDATE renders no condition and is a build error under ADR-020 rule 2 (AUDIT-001 N1, now tested).
…1 phase 4) docs_guard_test: every relica.X in public Markdown and llms.txt is exported, db.X(/tx.X( calls name methods of *relica.DB/*relica.Tx (other libraries' methods in the migration guides allowlisted per file), and internal/ is not named outside CONTRIBUTING and the v0.4 migration. It found two more calls that never existed: db.DB().Query in SECURITY.md and db.Exec in QUERY_ANALYZER_GUIDE. options_reach_test: every internal/core With* option has a public wrapper or an allowlist entry with its card (OPT-001, BUG-006).
… from RELICA_MYSQL_IMAGE 17 golangci-lint findings in test/: unused nolint directives, unhandled Close errors, a nested DSN branch (withParseTime), dialect name constants, spelling, an unused goroutine parameter. testutil starts mysql:8.0 unless RELICA_MYSQL_IMAGE names another image (for a newest-MySQL CI job).
…L job - pull_request targets main only; header comments describe GitHub Flow (the develop/release/hotfix branches are gone) - integration runs get -timeout=45m: every test starts a container, and the default 10m is shorter than the suite - go vet and golangci-lint run on the integration test module too (CHORE-001) - integration-tests-mysql-latest runs the MySQL suite on mysql:latest (ADR-019), not a required check; locally the upsert suite passes on MySQL 26.10
…(F1)
OrderBy kept the first word and the direction and dropped everything else
without an error: "salary + 1 DESC" ran ORDER BY "salary"; NULLS LAST,
COLLATE and a second column after a comma vanished — a different order on
every database. ozzo-dbx quotes the text before the direction as one name,
which at least fails in the database. Each argument is now a column with an
optional direction, an argument with "(" is used as is, anything else is a
build error pointing to OrderByExpr. Found by Fable review #2a.
…d (AUDIT-001 B/B2)
test/reachability_integration_test.go runs Rapid Type Analysis
(golang.org/x/tools v0.48.0, test module only) from every exported function
and every method of exported types of package relica — unexported methods
too, since users convert HashExp{...} or SplitAcronyms() to the sealed
interfaces in their own code — and fails on internal functions with no
public path, and on allowlist entries nothing needs. Allowlisted: optimizer
(OPT-001), security validator/audit log and their options (BUG-006).
GOEXPERIMENT is cleared for the load: with jsonv2 on, x/tools' SSA panics
on encoding/json/v2 generics.
Deleted, per the B2 verdicts: core.QuoteIdentifier (dialects quote),
core.WrapError/wrappedError, healthChecker.lastError, logger
Sanitizer.FormatParams/formatValue (dead since v0.17.2), util.IsCanceled,
util.WithTimeout, util.SanitizeString, util.SanitizeIdentifier, and the
tests that covered only them.
…laude Code plugin) skills/relica/SKILL.md (Agent Skills format): how to find the docs for the project's Relica version, rules from real bugs, an audit procedure, the upgrade procedure. scripts/audit.sh audits a project read-only: mistakes found in projects (H1-H3, M1-M7) and every v0.20.0 behaviour change that compiles (V1-V10: OrderBy/Select/GroupBy arguments, UPDATE/DELETE without Where, removed API, upsert actions, row locks, untagged and embedded fields, models without TableName, Count/Exists), plus style notes. audit_test.sh runs it offline on fixtures (*.go.txt, outside the Go module): bad/ fires every check a known number of times, clean/ must produce nothing; the chain style with the dot at the end of the line is covered. First real run (a project on v0.19) found five OrderBy calls that silently drop columns today. .claude-plugin/marketplace.json + plugin.json make the repo a marketplace (/plugin install relica --marketplace coregx/relica); claude plugin validate --strict passes. CI job agent-skill runs the tests and the validator. .gitattributes keeps *.sh LF.
…dule zip A go.mod under skills/relica/scripts/testdata made each fixture a nested module: excluded from the relica module zip, and tools generated a go.sum there (committed by mistake in the previous commit). The test renames go.mod.txt like *.go.txt when it copies a fixture.
…ines) The first consumer runs flagged OnConflict calls whose DoUpdate/DoNothing sat on the next line of the chain or in an if/else that picks one. The check now reads up to 8 following lines; POSIX awk only (tested with gawk and mawk 1.3.4, the Ubuntu runners' awk). Clean fixture covers both styles.
…ut Relica
The first run on a project that mixes ozzo-dbx (312 files) and Relica (12)
reported ozzo-dbx calls such as OrderBy("`id` ASC") as Relica findings.
Builder checks (V-group, I1-I3, I5, I6) now skip files importing ozzo-dbx
and not Relica; wrapper-typed files that import neither stay in. Clean
fixture covers it; tested with gawk and mawk.
On a large project it listed hundreds of model fields; most keep their name (Name → name) and only multi-word untagged fields change. It is a pointer for the upgrade, not a finding.
…n every database
README and WINDOW_FUNCTIONS_GUIDE passed the alias as SelectExpr's argument
("0 placeholders but N args") and used the alias rank, reserved in MySQL 8.
The alias goes inside the expression (rank_no); TestDocs_SelectExprArgsAreParameters
keeps such calls out of the docs, and window_functions_integration_test runs
the guide's forms on PostgreSQL, MySQL and SQLite (Fable review #2, F2).
…ompiler
V4 opened at Model(u).Update("col") or mq.Update("col") and closed at a
later statement's Execute(), so correct Model API code was a HIGH. The chain
now ends at a ";", an unbalanced closing bracket, or a line with balanced
brackets and no trailing dot; multi-line arguments stay inside the chain.
V5 matched echo.Context's QueryParams() in every handler: a grep cannot tell
the receivers apart, and the compiler reports the removed method itself.
Fixtures: clean/internal/handler (echo, Model API), two more V4 cases in bad.
Tested with gawk and mawk (Fable review #2 F4, claude-main skill review #2).
…s 2 when it cannot run From claude-main's review of the skill: - CI checks that plugin.json's version is the top CHANGELOG version, or above the last release while the top is [Unreleased] (the plugin cache keys on it). - audit_test.sh also runs testdata/bad as a git work tree (same findings; an untracked file is not audited) and removes only a fresh temp directory. - Files that use GORM, squirrel, bun or ent and not Relica are skipped like ozzo-dbx ones. - A versioned line inside a replace ( ... ) block is printed. - bash older than 4.3 (macOS 3.2) and a directory it cannot enter exit 2 instead of an empty audit with exit 0.
…pply to the combined result
a.Union(b).OrderBy("name").Limit(10) sorted and limited only the first
branch and appended all of b, while the docs promised the first 10 of the
union (Fable review #2, F1). Called after the first set operation, OrderBy,
OrderByExpr, OrderBySub, Limit and Offset now apply to the combined result;
called before it they still choose the first branch's rows. This is how the
SQL text reads and how jOOQ, Laravel, goqu and SQLAlchemy define it (v0.19
and ozzo-dbx used the first branch). Count/Exists ignore the combined
result's paging and keep a branch's.
a.Union(b).Intersect(c) was sent flat: SQLite computed (a u b) n c, while
PostgreSQL and MySQL bind INTERSECT first and returned a u (b n c) (F1b,
proven on live PostgreSQL and MySQL: {1,2} instead of {2}). The left side is
now parenthesized there, so a chain means the same on every database.
Two ambiguous forms are errors: an OrderBy in a branch without Limit/Offset
(ignored by PostgreSQL and MySQL), and a set operation after the combined
result's OrderBy/Limit/Offset.
Docs: Union godoc, AGENTS.md Set Operations section, SET_OPERATIONS_GUIDE
(precedence, errors, sorting by an expression via FromSelect; dangling
example paths replaced by the integration tests), MIGRATION_GUIDE, CHANGELOG.
Skill: audit check V11 for chains that call OrderBy/Limit/Offset after a set
operation (gawk and mawk).
Fable review #2 found guides pointing at example files that never existed and new v0.20.0 API missing from README and llms.txt. Fixed as classes, with guards: - TestDocs_LocalLinksResolve: relative links and repository paths in the public docs must exist and must not point into the unpublished docs/dev/. Dangling example lists now point at the integration tests that run those forms; private report links at AGENTS.md sections. - TestDocs_ExportedNamesAreDocumented: every package-level exported name is in AGENTS.md or README. 40 were not; AGENTS.md › Other Public API covers them (hooks, logger, MaskArgs, *Col comparisons, NotExists, NotBetween, pool options, naming blocks, builder types). README gains UPDATE/DELETE need Where (AllRows) and Table and Column Names/ScanMode; llms.txt a v0.20.0 rules list. Plugin install lines state Claude Code v2.1.275+.
An embedded struct was flattened whatever its type: sql.NullString became columns string/valid, a JSON type's Value was ignored, and embedded time.Time (no exported fields) produced no column at all, so it was silently not written and not read. A type is a single value when it is time.Time or the type or its pointer implements sql.Scanner or driver.Valuer (promoted methods count, as Go sees the type; GORM and go-jet draw the same line). An embedded single value is one column named by its db tag, else by Naming from the field name (NullString -> null_string, Time -> time, sql.Null[string] -> null). A db tag (name, pk, -) is allowed on it, and an embedded *T of such a type behaves like a named pointer field (nil = NULL). Embedded plain structs are still flattened; a tag on one or an embedded pointer to one stays an error; unexported embedded single values are skipped like unexported fields.
One(&t) with t a time.Time, sql.NullString or another sql.Scanner / driver.Valuer type walked the type as a struct of columns: nothing matched, and the value came back zero with err == nil (All the same). The generic One[T]/All[T] reach the same scanner. Such a destination is now an error that names the fix: "relica: time.Time is a single value, not a row: use Row(&v) or Column(&[]v)". A model that gets Scan/Value only by promotion from an embedded single-value field is still a row (that field is its column). The same types as a write model (InsertStruct, Model) are an error too. One validates its destination before the query runs, so a wrong destination (including One(&int) and model mapping errors) is reported on an empty result as well instead of being hidden behind ErrNotFound.
database/sql calls driver.Valuer only on the argument itself
(database/sql/convert.go). A field of type T whose Value method is on *T
alone was passed as T, so Value never ran: a struct type failed with
"unsupported type", and a defined basic type such as type Level int was
silently written as its underlying integer ("3" instead of "level-3",
confirmed on SQLite). A primary key of such a type bound the wrong WHERE
value, so Model().Update() matched no row without an error.
Field values for writes (InsertStruct, BatchInsertStruct, UpdateStruct,
Model Insert/Update/Upsert/UpdateChanged and the Model primary-key WHERE)
now go through argValue: when only *T implements driver.Valuer, the field
is passed as a pointer to a copy, which also works for a struct passed by
value (not addressable) and does not follow changes made after the query
is built.
A struct with only unexported fields, db:"-" everywhere, or nothing but
an unexported single-value type mapped to zero columns. Read, it matched no
column and came back zero with err == nil; written, it failed late with
"Insert requires a non-empty values map" or "primary key not found",
and BatchInsertStruct and UpdateChanged returned no error at all.
The struct walker now returns "relica: <type> has no columns: export
fields or tag them with db:\"...\"" for every path: InsertStruct,
BatchInsertStruct, UpdateStruct, the Model API, One (before the query) and
All.
Model Update, UpdateChanged, Delete, Find and Upsert replaced any mapping
error with a bare "model: primary key not found"; they now wrap the cause
("model: primary key not found: <cause>"), so it names the fix.
Column is documented (and worked in ozzo-dbx) as scanning the first column of every row. Relica passed one destination to rows.Scan, so any query with more than one result column failed with "expected N destination arguments in Scan, not 1". The first column now goes into the slice and the other columns are read into one shared any sink and dropped; any takes every driver value as is, so their types do not matter. The first column is converted exactly as before. The scan loop moved to scanFirstColumn so Column reports every failure through one hook call.
isValueType counted methods promoted from an embedded field, so a plain
struct such as Audit{sql.NullString; X int} became one column written
through NullString.Value: X was never written or read. An unexported
embedded struct whose Scan/Value came by promotion was skipped entirely.
A type is now a single value only when its Scan or Value is its own.
reflect cannot tell a declared method from a promoted one, so a method that
an embedded field also has counts as promoted and the type is flattened; a
wrong guess fails on a missing column instead of dropping fields. A method
no embedded field has must be declared, so Date{time.Time} with its own
Scan and Value stays one column (time.Time has neither). isValueRow is gone;
models, One/All destinations and embedded fields use the one predicate.
Base{sql.NullString} embedded in a model is now column null_string (was
base); Audit{sql.NullString; X int} is null_string and x.
SelectQuery.Model bound each primary-key value with reflect's Interface(), so a key type whose Value method is on *T alone was passed by value and database/sql never called Value: db.Select().Model(&m) looked up the raw value (5 instead of level-5) and returned ErrNotFound for an existing row. It now goes through argValue, like every other write and Model API path.
… are errors Fable review #3 F1/F2. Under ScanRequireFields (default): - Select("id", "email") into a struct whose tag was renamed to mail read email and dropped it, err == nil: a column the code named must land in a field. Select() / Select("t.*") of a wider table list nothing and keep ignoring extra columns. - Two result columns for one field (Select("a.id", "b.id"), SELECT * over a join where both tables have id) let the later column overwrite the earlier one silently; now an error that says to alias one. ScanLenient keeps the ozzo-dbx behaviour. Integration tests on PostgreSQL, MySQL and SQLite (test/scan_columns_integration_test.go), failing before.
Fable review #3 F4. reflect can neither read nor write an unexported field, so it is no column — fine when untagged, but a db tag (a db:",pk" key included) or an embedded single value (its Scan/Value) asked for a column and was dropped without a word: an embedded unexported key type vanished and Update failed far from the cause. Both are errors now; untagged unexported fields stay ignored. Docs: AGENTS.md also says that a type that declares its own Scan/Value and embeds a Scanner/Valuer is flattened.
…godoc Fable review #3 F3, F6, F7, F8: - AGENTS.md, MIGRATION_GUIDE and the addSetOperation error recommended c.Union(sorted), which the code rejects (a branch ORDER BY without LIMIT orders nothing): a sorted and limited compound can be a branch, a sorted one only a FromSelect source. - ExplainAnalyze is PostgreSQL and MySQL only (SQLite returns an error). - internal/core Count godoc still described the pre-v0.20.0 set-operation paging. - WINDOW_FUNCTIONS_GUIDE generated SQL shows what Relica renders (quoted ORDER BY alias, AS as written).
Fable review #3 F5: the chain-opening match ran on the whole line, so // Danger: db.Delete("t").Execute() ... in a repo's own notes was a HIGH. nocomment() (strings respected) strips the comment before the match; clean fixture gains both comment forms. The Not greppable list now names the v0.20.0 scanning errors and the chains split over statements that V4/V11 cannot follow. gawk and mawk PASS.
…xpression Fable review #3 F9. In("id", sub), Eq, Between, HashExp, Coalesce/Case arguments and Set values bound a *SelectQuery as a query argument: ToSQL showed a plain placeholder and the driver failed late with "unsupported type relica.SelectQuery". coreArg turns it into a value whose Err() makes the condition or SET value a build error (core.ExpressionError), so nothing is sent; the error says to use q.AsExpression(). HashExp and Set copy the caller's map only when a value needs converting. Insert/Upsert values are bound as plain arguments by design, where database/sql already rejects it.
Suggested by gofro after rebuilding on bd7cafe: Select(..., "COALESCE(u.full_name, '')") into a field tagged full_name — PostgreSQL names the column coalesce, so the field stayed empty on v0.19 and is an error on v0.20.0 (ScanRequireFields). The check reads only the Select/AndSelect call's own string arguments (a Where("LOWER(x) = ?") on the same line is not one). Fixtures: two bad, two aliased forms clean. gawk and mawk PASS. AGENTS.md states the alias rule.
gofro: V12 at 2edb7e4 gave 5 false positives on gofroprom — scalar subqueries named by the outer SelectSub alias, and Row()/Column() reads, which scan by position. V12 now follows the chain over lines (scan()) and reports a function column without AS only when the same chain calls All or One. Multi-line Select arguments are read too: the one true positive was a multi-line Select(...) ending in All, which the single-line version missed. On gofroprom main: 1 finding (that one), was 5 false + 0 true. Fixtures: multi-line bad case; Row, Column and SelectSub scalar subquery clean. gawk and mawk PASS.
CHANGELOG [Unreleased] -> [0.20.0] - 2026-10-27; ROADMAP current state. CHANGELOG: the Count/Exists line on set operations described the old first-branch rule, and the set-operations entry broke off mid-sentence.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v0.20.0: breaking fixes for every place where Relica returned a different result than the code asked for, with no error. Each case now does what the code says or fails before anything is sent. Full list: CHANGELOG
[0.20.0]; upgrade path: docs/MIGRATION_GUIDE.md › v0.19.x → v0.20.0.Behaviour changes
DoUpdate(cols...), the newDoUpdateAll()orDoNothing(). MySQLDoNothing()keeps the existing row. NewWithMySQLUpsertSyntax.WithNaming. Embedded structs are flattened on write; an embedded single value (time.Time,sql.NullString, a Scanner/Valuer) is one column.ScanRequireFieldsby default (ScanLenient,ScanStrict,WithScanMode).One/Allinto a single value is an error.Whereor the explicitAllRows(). Row locks work only inside a transaction.OrderBy/LimitafterUnionapply to the combined result, anda.Union(b).Intersect(c)means the same on every database.Count()/Exists()follow whatAllreturns withGroupBy,Distinct, set operations and CTEs.OrderBytakes "column [ASC|DESC]"; expressions go toOrderByExpr.NewDB(useOpen) andQuery.QueryParams()(useParams()).Also: 16 fixes (see CHANGELOG › Fixed), docs corrected against the code with tests that guard links, exported names and
SelectExprexamples, and the agent skillskills/relica/withaudit.shand its fixture tests.Verification (local, before this PR)
go build,go vet,gofmt -l,go test ./...,golangci-lint(root andtest/module): cleanskills/relica/scripts/audit_test.sh: PASS with gawk and mawk