Skip to content

Releases: kr9ly/sqlshape

v2.0.1

Choose a tag to compare

@github-actions github-actions released this 27 Sep 00:10

Changed

  • A -- @migrate backfill declaration left in schema.sql after its step is applied is now
    an error (diff exits 1 and the UPDATE is not printed) when nothing else about its table
    changes. Before, the UPDATE was silently printed again, and applying it overwrote data the
    application had written since. A backfill in a step that does change the table — a new
    column, a new CHECK / UNIQUE / foreign key on existing rows — is emitted as before.
  • mysql.Verify no longer fails on a sql_mode name MySQL 8.4 does not define (a server of
    another version or lineage runs with some): it compares the modes it knows and, on a
    mismatch, says which names it left out.

Fixed

  • On MySQL, sqlshape apply runs the DDL under the sql_mode the schema declares (the 8.4
    default when it declares none), the same setting the plan was verified under. Before, the
    DDL ran under the server's global mode, so the pre-check and the real run could disagree.
  • On MySQL, a backfill of an existing column is printed before the keys, CHECK constraints
    and foreign keys the step adds, so apply no longer fails on rows the backfill was meant
    to fix.

Documentation

  • The README leads with what a reader needs to decide: what is checked, the supported
    versions, that checking needs no database, and how sqlshape differs from generators and
    ORMs. A Limitations section states what is out of scope, including that a server of an
    undeclared version still runs but its divergences are not guaranteed to be caught.
  • The MySQL error catalog moved to docs/mysql-errors.md with an
    index by error number; docs/mysql.md now leads with the type table, the
    DSN requirements and the runtime. docs/migrations.md starts with the
    prerequisites and gained a section on running migrations across several environments.

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 15:29

Added

  • MySQL 8.4. A schema.sql that declares -- sqlshape: mysql 8.4 is checked by a MySQL
    analyzer: the parser and lexer are the server's own, the function catalog is read from the
    server's source, and the verdicts (types, nullability, errors, the ONLY_FULL_GROUP_BY check)
    are measured against a running mysqld. One, the obligations and the failure modes work
    through the same contract PostgreSQL uses, with the constraints named as MySQL reports them
    (PRIMARY, orders_ibfk_1, t_chk_1) and INSERT IGNORE / ON DUPLICATE KEY UPDATE /
    REPLACE judged as the server runs them. What has no MySQL counterpart is listed in
    docs/mysql.md.
  • MySQL triggers, stored procedures and functions, and events: their bodies are analyzed
    (NEW / OLD, variables, control flow, cursors, SIGNAL / RESIGNAL / HANDLER, CALL),
    a statement inherits the failure modes of the triggers it fires and the routines it calls,
    and the server's own refusals (a trigger writing its own table, 1442, among them) are
    reported. -- sqlshape: error <code> = <Name> names a raised error, as on PostgreSQL;
    -- sqlshape: not null above a CREATE FUNCTION declares it never returns NULL.
  • On MySQL the checker also reads LOAD DATA (an INSERT of the file's rows, with the server's
    own 1263 for a NULL field in a NOT NULL column), LOCK TABLES / UNLOCK TABLES,
    SELECT ... INTO OUTFILE / DUMPFILE (no result set) and a SELECT with a trailing
    FOR UPDATE / FOR SHARE; the loader applies ALTER VIEW. Inside a routine or trigger body
    the first three are the server's own 1314.
  • MySQL writes through views, analyzed as the server runs them (each rule measured): an
    INSERT / REPLACE, UPDATE or DELETE through a merged view lands on its base table,
    with the base table's own failure modes and the view's own (WITH CHECK OPTION as the
    violation 1369 -- on INSERT and REPLACE too -- and a base column with no default the
    statement leaves unassigned as the view's 1423, both matched by Violates under the view's
    name), and the server's refusals are statement errors under the server's own codes: the
    view's updatable / insertable flags are computed over its FROM leaves the way
    sql_resolver.cc computes them when it merges, a join view follows the server's rules for
    each write kind, REPLACE and DELETE never reach one, and a subquery reading the written
    view itself is 1093. The rules are docs/mysql.md's.
  • The run-time failures MySQL decides by a constant's value are judged (each measured against
    mysqld; the rules are docs/mysql.md's): a literal a column can never store
    (1264 / 1265 / 1292 / 1366 / 1406 / 1416, in strict mode outside IGNORE), a temporal
    literal the server cannot read as its type (1525) or a TIMESTAMP column cannot hold, the
    spatial rules (1210 / 3037 / 3516 / 1416), constant arithmetic the server cannot compute
    (1690), the constant conversions a strict write escalates (1292; a temporal column compared
    with a constant string that is no datetime is 1525 whatever the statement), and the
    functions that judge a constant argument's value: INET_ATON / INET6_ATON / UNHEX /
    STR_TO_DATE fail a strict write outside IGNORE (1411; STR_TO_DATE under a port of the
    server's own extract_date_time), UUID_TO_BIN / BIN_TO_UUID (1411) and PERIOD_ADD /
    PERIOD_DIFF (1210) fail every statement whatever the mode, and NAME_CONST, ESCAPE,
    NTILE, NTH_VALUE and MATCH ... AGAINST are refused at resolution, rows or none. Each
    is the statement's own error where the server evaluates it before reading rows, and a
    failure mode Violates matches by number ("1416", "1690", "1292", "1411") where it
    runs per row. A prefix key (UNIQUE (c(10))) or an expression key (UNIQUE ((n * 2))) is
    a violable key (1062) for the writes that assign its columns.
  • A MySQL function call argument carrying an alias (f(x AS a), the loadable function
    syntax) is refused in the server's own order (1582 / 1583 / 1584, a data dictionary
    function 3566), and an explicitly scoped system variable read must match the variable's
    scope: @@session.x of a GLOBAL-only variable and @@global.x of a SESSION-only one are
    the statement's 1238 wherever the read sits, over a scope table generated from the server
    source (sql/sys_vars.cc) and pinned against mysqld entry by entry; a plugin's or
    component's variable is not judged.
  • The statement facts the One proof and the obligations are judged on are tested against a
    running server the way the checker's types are: x/stmtprobe generates schemas with rows
    and statements over them (joins, views, derived tables, CTEs, EXISTS / IN subqueries,
    GROUP BY, UNION ALL, writes), and refutes every claim of the facts -- a predicate holding
    on every row, a fixed column, an at-most-one-row proof, the value a write stores -- with the
    rows the server returns, on PostgreSQL 17 and MySQL 8.4. The same probe judges the predicted
    failure modes: writes against a schema carrying every constraint kind (named and unnamed
    keys, foreign keys with each ON DELETE action, checks, NOT NULL; IGNORE / REPLACE /
    ON DUPLICATE KEY UPDATE and ON CONFLICT), with every constraint error the server raises
    required to be a predicted violation under a key Violates matches.
  • MySQL trigger, procedure and function bodies are tested against the server's own
    CREATE-time verdict the same way (the body probe in check/mysql/internal/analyze): every
    construct the server refuses when the body is created, mixed into generated bodies, must
    be refused by the checker too, and nothing the server accepts may be.
  • MySQL's own test corpus (mysql-test/t: 1,281 files, 137,000 statements) replays against a
    running mysqld and the analyzer side by side (check/mysql/internal/analyze's corpus probe,
    the counterpart of PostgreSQL's regress probe): each file on a server of its own, the
    analyzer's schema rebuilt from SHOW CREATE after every DDL under the session's sql_mode,
    a SELECT's columns compared by name, type family and nullability, an error by number; the
    remaining disagreements (2,525 statements) are pinned one by one as a baseline, and a new
    one fails the build.
  • mysqltest.StartOwn boots a server of its own even under mysqltest.Main, for a test that
    changes accounts, global variables or other databases.
  • postgres.WrapError(err) and mysql.WrapError(err) give an error from a statement run
    outside Run / Exec the wrapping Violates judges.
  • sqlshape.Error(code) declares a schema's named error in Go (var OrderTooLarge = sqlshape.Error("30001")); go vet checks the declaration against the schema both ways, an
    expect line may use the Name, and Violates accepts the value.
  • -- sqlshape: server <var> = <value> declares the server settings the checker's judgments
    depend on (MySQL: sql_mode, lower_case_table_names, max_sp_recursion_depth); the
    checker follows them, and mysqltest.Start starts the server with them.
  • The MySQL runtime, github.com/kr9ly/sqlshape/mysql/v2 (Run, Collect, First, Exec,
    Get / Find / ExecOne, ConstraintError, Violates) over database/sql, and
    github.com/kr9ly/sqlshape/mysqltest/v2, which boots the mysqld on PATH with the
    application's schema; a package whose TestMain runs through mysqltest.Main boots one
    server per set of declared settings and hands it from test to test instead of booting one
    per Start. examples/5-mysql shows the whole path.
  • sqlshape diff, apply and verify-schema on MySQL: both sides are read as the server's own
    SHOW CREATE output, the target canonicalized in a scratch database on the same server, and
    the plan written in MySQL's own DDL, statement by statement. Tables with their options,
    columns with their position, keys, foreign keys, checks, views, triggers, routines, events
    and partitions are compared; seeded rows are not.
  • Partitions are migrated on both dialects: a partitioned table is created, a partition added,
    dropped (under a -- @migrate drop declaration, since it takes its rows), attached, detached,
    its bound moved with the rows that no longer fit moved along, and a populated table can be
    partitioned after the fact, unpartitioned, or repartitioned, the rows redistributed by the
    server. MySQL covers RANGE, LIST, COLUMNS, HASH, KEY, LINEAR and subpartitioning
    (a partition's explicit SUBPARTITION name list included),
    with -- @migrate drop partition <table>.<partition> for a partition that goes.
  • The migration planner is tested against a real server the way the checker is: generated
    schema pairs, every kind of change the diff can report, applied with rows in the tables on
    PostgreSQL 17 and 18 and on MySQL 8.4, with the plan required to land on the declared schema
    and leave nothing to plan afterwards (docs/migrations.md).
    The orderings and omissions this found are fixed and pinned as regression tests.
  • Row-level security, composite types, rules, extensions, schema moves, standalone sequences,
    procedures, aggregates, EXCLUDE, partial and expression indexes, NULLS NOT DISTINCT and
    DEFERRABLE are handled by the PostgreSQL migration plan; FULLTEXT / SPATIAL / functional
    / INVISIBLE keys, ROW_FORMAT, table charset changes and column comments by MySQL's.
  • x/dialect.Type.ElemNotNull: an array whose elements are provably not NULL
    (array_agg((a, b)::t), an inner-joined NOT NULL column) binds to []T without the "may
    contain a NULL element" note; col:",notnull" on an array field asserts the elements too.
  • -query=pkg.Func,pkg.Other:one registers a program...
Read more

v2.0.0-rc.2

v2.0.0-rc.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Sep 05:39

Added

  • cmd/sqlshape/internal/vet gained docs_test.go: a harness that reads the go/sql fences out of
    docs/checks.md and checks.ja.md at test time (both the code and the diagnostic wording a
    Rejected fence's trailing comment claims), completes each into a compiling program under
    testdata/src/docsex_*, runs it through the checker, and asserts that every diagnostic docs
    claims is a literal substring of one the checker actually produced, with nothing left over. It
    covers "Result columns bind to fields by name", "A column that may be NULL needs a field that can
    hold NULL", "Fix a unique key by equality", "Bulk loading with COPY", and "Name the errors a
    trigger raises" (both dialects) so far, in both languages -- not the full set of fences yet (see
    NOTES.local.md and the package doc for what is not wired up and why).
  • x/dialect.Type gained ElemNotNull: a dialect that can prove an array's elements are
    never NULL sets it (false, the default, means "unknown", not "may be NULL"). PostgreSQL
    sets it for array_agg(x) where x is itself never NULL (a row constructor such as
    (a, b)::T, an inner-joined NOT NULL column, a whole-row reference), for a literal
    ARRAY[...] whose elements are all provably not NULL, and for ARRAY(SELECT ...) over a
    not-null single output column, propagating it through subqueries, CTEs, set operations and
    views (a view's stored column snapshot carries it, schema.ViewColumn.ElemNotNull, refrozen
    with the nullability). A plain array column never qualifies, since PostgreSQL declares nothing
    about a column's own elements, so col:",notnull" on an array field asserts the elements too.

Changed

  • x/expand: an if / with / range over a path an earlier control in the same lineage
    already decided (the same {{if .Status}} appearing twice, say, once in a column list and
    once in VALUES) now follows that decision instead of branching it again, in both the full
    and the sparse expansion; a template no longer yields impossible expansions the runtime's
    evaluator, reading the value once per occurrence, was never actually going to produce.
    dialect.Advice now carries the table and column it is about, and -strict reports such
    advice only in a package whose statements touch that relation or column (schema-wide advice
    still applies everywhere), so an enum column no longer lectures every package under
    -strict. A pointer parameter used inside the then branch of {{if .X}} / {{with .X}} on
    that very path is recognized as not NULL there (an expansion's GuardedTrue covers only the
    exact path the taken guard proved, not paths below it, and the else branch proves nothing).
    PostgreSQL: array_agg((i.sku, i.qty)::order_item) into []Item keeps each nested row
    field's own nullability through the cast to a named composite type, instead of discarding it
    (a row constructor is never NULL; array_agg of a scalar does pass a NULL element through,
    unaffected). Building on that: the checker's standing "may contain a NULL element" note
    (and its -strict rejection) on an array result no longer fires when dialect.Type.ElemNotNull
    says the elements are provably not NULL, so array_agg((i.sku, i.qty)::order_item) into
    []Item is now accepted with no note at all, while array_agg of a plain nullable column,
    a literal ARRAY[...] with a NULL element, and an ordinary array column keep the note (and
    the -strict rejection) unchanged. A parameter compared against a domain column carries the domain (PostgreSQL
    resolves the placeholder to the base type, so Param.Type is taken from the compared
    column), so -strict's "carries domain yen as a plain int64" advice reaches parameters
    too, and a mismatch names the domain (SQL expects email).

  • MySQL: -- sqlshape: not null above a CREATE FUNCTION declares the function never returns
    NULL, the same directive PostgreSQL already has; a call then types as NOT NULL. A PROCEDURE
    or TRIGGER still refuses the directive (neither returns a value for it to describe).

  • PostgreSQL after a third adversarial round against a running server (8 lanes, 23 findings, each a
    regression test in adv_*_test.go), the failure modes first. TRUNCATE of a table another table's
    foreign key still references (no CASCADE, the referrer not in the list) is reported as certain to
    fail (0A000); ON CONFLICT (cols) absorbs a unique constraint only when it can be the arbiter, so a
    DEFERRABLE key (55000) or a partial index whose predicate the ON CONFLICT ... WHERE does not
    repeat (42P10) keeps its 23505 in the list, and ON CONFLICT ON CONSTRAINT <exclusion> DO UPDATE
    is reported as certain to fail (42809); ON CONFLICT DO NOTHING without a target absorbs the
    exclusion constraints and partial unique indexes too, and is certain to fail (55000) when any
    key of the table is DEFERRABLE. A NOT NULL that a domain, not the column, declares is
    keyed by the domain's name (schema-qualified unless public): PostgreSQL reports no table or column
    for it, and postgres.ConstraintError.Key() now returns the domain's name for that error, so
    Violates(err, "email") matches what the checker predicted. ORDER BY / GROUP BY resolve an
    output name the way PostgreSQL does: two output columns of one name are ambiguous (42702) unless
    they are the same expression; the ungrouped-column error names its table ("t1.v"); an error the
    select list and the WHERE share is reported at the select list's position. The result of a
    set-returning function in the select list (unnest(arr)) is nullable whatever its argument is:
    an element can be NULL when the array cannot. The One proof sees through a cast on the grouped
    column, takes a one-element = ANY(ARRAY[v]) and a NOT NULL column's IS NOT DISTINCT FROM v
    for equalities, and matches a partial index's predicate with the literal casts a statement spells
    out; col = NULL fixes nothing (it is never true), so it neither pins nor proves single, on MySQL
    too. SET col = DEFAULT stores the column's literal default, so a transitions declaration
    judges it as a transition to that state (a non-literal default is refused as before, with a
    message that says why). -strict rejects []T / [N]T for an array whose Go element cannot be
    NULL (integer[] into []int32, a composite array into []Item), and both modes note that
    PostgreSQL never promises an array's elements are non-null even when the column is; interval
    into time.Duration carries a Lossy note (a month flattened to 30 days disagrees with the
    server's calendar arithmetic). vet no longer skips a sqlshape.Query[R, P] called through a
    package-level variable (var q = sqlshape.Query[R, P]; q(...)), and checks the fields an {{if}}
    reads through a nested call ({{if gt (len .Xs) 0}}) against P. Migrations: rewriting a
    generated column's expression, or its STORED / VIRTUAL kind (PostgreSQL 18), drops and restores
    the indexes, constraints and referencing foreign keys the DROP COLUMN would take with it, instead
    of losing the index or emitting DDL the server refuses (2BP01); a kind-only change is no longer a
    no-op. An unfiltered / waive written above a CREATE TABLE is told where it belongs. Kept as
    they were, and now written down: paired requires the write, not matching values; RETURNING a
    sensitive column is reading it.

  • pgtest and mysqltest are sqlshape's own test tooling, like check/*: they boot a real server
    for the examples, the oracles and the conformance tests, and carry no compatibility promise. An
    application tests its database with the server it runs on; the agreement between the checker
    and the server is sqlshape's to keep. The documentation no longer presents them as part of the
    API, and is reorganized for a reader who uses one database: docs/postgres.md and
    docs/mysql.md gather what is each database's (the declaration, the Go type table, the
    constraint names and error numbers, the runtime, what has no counterpart), the README has a
    quickstart per database, and checks.md / runtime.md are written for both.

Added

  • sqlshape.Error(code) mirrors a schema's -- sqlshape: error <code> = <Name> annotation (a
    trigger's or routine's own raised failure mode) in Go: var OrderTooLarge = sqlshape.Error("30001"). go vet checks it both ways -- the declaration's code must be one the
    schema declares under that exact Name, no two declarations in a package may claim the same code,
    and every named failure mode a package's statements can raise must have such a declaration
    somewhere the checker can reach (a Fact carries the binding across packages, the way a declared
    type's does). An expect line may spell the failure by its code or by the Name, whichever reads
    better. postgres.Violates / mysql.Violates now take any ~string, so a declared
    sqlshape.Failure reads the same as the raw code always did; neither reads the schema, so they
    judge only by the code the value holds, never by Name. The PostgreSQL runtime wraps a custom
    SQLSTATE into ConstraintError for any statement with an expect line at all now (it cannot
    resolve a Name back to the code the checker matched statically). examples/3-database-api,
    examples/4-everything and examples/5-mysql declare their triggers' and functions' errors this
    way and test Violates against a running server.

  • sqlshape diff, apply and verify-schema work on a MySQL schema. Both sides are read as the
    server's own SHOW CREATE TABLE / SHOW CREATE VIEW, parsed by the loader that reads
    schema.sql; the target is canonicalized in a scratch database on the -db server (dropped when
    done), or on a mysqld from PATH with -from. Compared: tables, columns (their positi...

Read more

v2.0.0-rc.1

v2.0.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 10 Sep 07:49

Changed

  • The checker is the product; the runtime is a library per database. The root module
    github.com/kr9ly/sqlshape/v2 now holds only the declarations (Query, One, Stmt, Render,
    the row-binding rules) and has no dependencies. Running a statement on pgx is the module
    github.com/kr9ly/sqlshape/postgres/v2: postgres.Run(ctx, db, stmt, p), Collect, First,
    Exec, Get / Find / ExecOne for One, and MatView, Copy, Batch, LoadUserTypes,
    ConstraintError, Violates, ErrNoRows moved there (the methods stmt.Run(ctx, db, p) are
    gone: a module cannot add methods to another's type). Labelled and UnknownLabelError stay in
    the root. pgtest is its own module. The PostgreSQL analyzer and migration tools are the module
    github.com/kr9ly/sqlshape/check/postgres/v2, the MySQL analyzer check/mysql (was mysql);
    both are tool-facing, imported by the binary and pgtest. An application's go.mod carries the
    root, its runtime and its own driver, nothing of the checker's.
  • The markers the checker recognizes are configurable: -query=pkg.Func,pkg.Other:one registers
    a program's own generic marker functions, read like Query / One. A program may run its
    statements through a runtime of its own; what only sqlshape's runtime promises (the rendered SQL
    is the checked SQL, violations come back under the expect line's names, One rejects a second
    row) is stated in docs/runtime.md.

Added

  • MySQL, a first slice. A schema.sql that declares -- sqlshape: mysql 8.4 is loaded by the
    MySQL schema loader and every Query is judged by the MySQL analyzer: the statement's own errors
    (unknown table or column, ambiguity, syntax, with MySQL's message and error number), result
    columns against R by name, type and nullability, parameters against P where their context
    types them (compared with or assigned to a column, LIMIT, the argument positions a function
    declares). Single-block SELECT, INSERT, UPDATE and DELETE over base tables. Expressions are typed
    by the server's own rules, read out of its source: arithmetic promotion (Item_num_op), CASE /
    IF / COALESCE / GREATEST through field_type_merge, casts, aggregates, and every function of
    the native registry through its Item class family and resolve_type facts, so a SUM is a
    nullable decimal, a comparison a bigint(1) a Go bool can carry, LENGTH(?) types its
    placeholder as a string. The rules are checked against a real mysqld (a local test, not a CI
    dependency): every registry function over representative argument types, 5,267 statements,
    agrees with the server on type and nullability but for 10. Subqueries (scalar, IN, EXISTS,
    ANY / ALL, correlated), derived tables (LATERAL too), views, common table expressions
    (recursive too), UNION / EXCEPT / INTERSECT and INSERT ... SELECT are analyzed with the
    server's rules for what they produce: a scalar subquery is NULL unless its query is guaranteed a
    row, a recursive CTE's columns are always nullable, a derived table the server materializes
    retypes a small integer expression to an int, a merged view is updatable. What has no rule yet
    (user variables, the temporal hybrids such as ADDTIME) is accepted with a note; One,
    MatView, Copy and the obligations are not supported yet. Parameters stay {{.X}} in the
    template; the analyzer speaks ? to MySQL.

Changed

  • The binary is its own Go module, github.com/kr9ly/sqlshape/cmd/sqlshape/v2, under the GNU
    General Public License v2; install it with go install github.com/kr9ly/sqlshape/cmd/sqlshape/v2@latest.
    Everything a checked program imports stays Apache 2.0. Every module's path carries the /v2
    major-version suffix Go requires of a 2.x module, so an import of 1.x is
    github.com/kr9ly/sqlshape and of 2.x github.com/kr9ly/sqlshape/v2. The modules of the
    repository release together under one version, tagged vX.Y.Z and <module>/vX.Y.Z on the
    same commit.

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 02:38

Added

  • PostgreSQL 18. schema.sql declares its version with -- sqlshape: postgres 18, and the schema
    and every statement are judged with 18's grammar and catalog: RETURNING old / new (with
    WITH (OLD AS ..., NEW AS ...)), WITHOUT OVERLAPS keys and PERIOD foreign keys, NOT ENFORCED
    constraints (which are no failure mode), named NOT NULL constraints, VIRTUAL generated columns,
    and 18's own checks (a JSON_VALUE DEFAULT whose collation is not the RETURNING type's, a JSON path
    that is not a jsonpath, stricter datetime and aclitem input, an outer-level aggregate over a nested
    CTE). pgtest and the migration commands run the declared version's PostgreSQL; diff, apply
    and verify-schema warn when the database runs another major version than the schema declares.
  • One proves a single row through a temporal key: the scalar columns fixed by equality and the
    range column equal to a known value or containing a known point (valid_at @> {{.Day}}::date).

Fixed

  • SELECT DISTINCT ON expressions are matched to the leading ORDER BY expressions by expression,
    not by where each was written; the same expression in both no longer fails with 42P10.
  • An UPDATE that assigns a generated column reports PostgreSQL's message (column "b" can only be updated to DEFAULT) rather than INSERT's.

Changed

  • schema.sql must declare its PostgreSQL version (-- sqlshape: postgres 17); a schema without
    the line is not read. Add the line to move from 1.1.
  • The parser (libpg_query) is compiled to WebAssembly and run by wazero instead of being linked
    through cgo. go install no longer needs a C compiler, and the binary is a plain Go build.
    Parse trees, error messages and positions are unchanged.
  • Prebuilt binaries for every platform, cross-compiled by one job: Linux, macOS and Windows, amd64
    and arm64 each (Windows and Intel macOS are new).

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 08:07

Added

  • Obligations: schema.sql declares, above a CREATE TABLE or CREATE VIEW, what every statement
    touching the relation must do, and vet judges each statement against it
    (checks.md, Part 2). The general form is
    -- sqlshape: require <what> [on <kinds>], where <what> is an SQL predicate, pinned(col),
    immutable(col), via view, never, paired(table) or single.
  • aggregate root (child, ...) [lock version]: the tables form one aggregate; children are reached
    through the root's key, one statement touches one aggregate, and with lock every write names
    the root's version.
  • transitions col: a -> b, b -> c | d: a status column is a state machine; an UPDATE setting it
    must compare-and-set from a declared predecessor.
  • sensitive label: col, col and context name: may read label: labelled columns are readable
    only in a context that allows the label.
  • context name: require ...; waive ...: obligations that differ per caller, selected by a
    package's // sqlshape: context name comment, vet's -context, or check -context.
  • -- sqlshape: waive table [obligation] in a statement or a view definition: an opt-out,
    reported with -strict.
  • Cross-table predicates: require EXISTS (SELECT 1 FROM parent p WHERE p.id = parent_id AND ...)
    is discharged by a join or subquery that witnesses it.
  • sqlshape check: the same judgments for SQL outside Go (files or stdin), every judgment printed
    as an audit line, exit code 1 on a failure.
  • The internal/facts package: what a statement provably does, in a form that does not depend on
    the SQL dialect, so that the obligation checker can be shared with other dialects later.

Changed

  • visible where, -require-columns, -no-table-reads and -no-tables are now obligations
    (require <expr> on read, require pinned(col), require via view, require via view on all)
    and are judged by the same checker. Judgment is per occurrence of a table: a self join or a
    subquery reading the table again owes the obligation again; -require-columns used to accept a
    statement that pinned the column anywhere in it. A row-level security policy or a composite
    foreign key can discharge a pin. MERGE's ON is judged like a WHERE.
  • Writes are counted by what the statement does to the table: each MERGE branch is a write of its
    own kind, an INSERT ... ON CONFLICT DO UPDATE is an insert and an update, TRUNCATE is a
    delete, and a write through an automatically updatable view is a write to the base table.
  • pinned on an UPDATE is satisfied by the WHERE only; assigning the column in SET is not a pin.
  • A -- sqlshape: directive above a statement that takes none (ALTER TABLE, COMMENT ON) is a
    schema problem instead of being ignored.
  • The docs are split: the README is an overview; docs/ has checks, templates, runtime,
    migrations and flags, in English and Japanese.

Fixed

  • A vet failure shared by every branch of a template is reported once without the branch tag.
  • A diagnostic on a column referenced only in GROUP BY had no position.

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 13:12

Full Changelog: v1.0.0-rc.1...v1.0.0