Changelog
All notable changes to retrofit are recorded here, newest first, in the categories of Keep a Changelog. Before 1.0 retrofit promises no compatibility: a change is disclosed here, not prevented.
[Unreleased] records what the builds since the last release changed; a
release renames it to its version. Its fixed Log format heading records
every change to the oplog’s vocabulary, parameters, headers or seal bytes:
what changed and what a user does, usually regenerating the log. CI fails a
commit that changes internal/oplog/catalog/*.golden.json,
internal/oplog/serialize/ outside its tests, or
internal/seal/testdata/leafbytes_*.golden but adds no item under that
heading itself (make logformat-check); with make hooks, the
commit-msg hook refuses it at git commit. A commit whose serializer edit
changes none of the vocabulary, parameters, headers or seal bytes may
instead end its message with a Log-format: unchanged trailer; a reader
refusing a log it used to accept is a format change. A change to either set
of goldens always needs the item.
[Unreleased]
Added
- This change log. CI requires a “Log format” entry for any change to the
oplog catalog golden, the serializer or the seal’s leaf-byte goldens. A
serializer edit that leaves the format unchanged may say so with a
Log-format: unchangedcommit trailer instead. - Each release archive carries a man page per command and the bash, zsh
and fish completion scripts, and the Homebrew cask installs them, so
man retrofit-migrateworks afterbrew install. migrateandsnapshottake--allow-unnamed-label <label>(repeatable). It attests that a labelled dataset was left unnamed on purpose, such as a test-onlyfixtureslabel on a long-lived target. Theapply-risk-data-exclusionswarning no longer counts that dataset, and its hint names this flag beside--label. Each value names one exact label, so a dataset added later still warns. An attestation that answers nothing prints a note that it had no effect.
Fixed
- The
dml-chain-near-misswarning a data mint raises compares a new chain only with existing chains of its own band (reference, seed or history).data capture --label fixtureson a table that also holds acleansechain no longer warns that a label drift forks the chain, or suggests re-running with the other chain’s label-set: a chain of another band is never the one a capture supersedes. data capture’s verdict line printsnot comparable:once before the reason instead of twice, anddata capture --dry-runrehearses a table it would capture aswould capture:, notwould captured:.migrate’sdata-replace-prologueanddata-replace-cascadewarnings say a replaced table “is replaced by its full observed state” andreplacing <table>, not “cleared” and “clearing”: a keyed replace upserts the observed rows and deletes only the keys the state lacks, and never empties the table.op add CreateIndex --helpspells the per-key operator-class flag--key_opclass, whichop addandop reviseaccept beside--key_opclasses. The misspelling--key_opclasseis refused.initagainst an existing.gitattributesprints a note naming each line it appends, instead of changing the file silently.- A data file’s record marker line that leaves out a required token
(
kind=,seq=or thesrc=provenance) is refused naming the missing token, not as carrying an “unrecognized or mis-ordered” one. Every marker-line refusal (G-e) now spells the marker grammar as its remedy, and the shared repair hint names the data file;data checkand the other corpus readers no longer append a line about a “sidecar”. identifyandmigrate --from-dumprefusefrom-dump-schema-absentfor a dump of a database that holds neither the (non-public) schema nor any of its objects, and point atmigrate --from root. They matched such a dump to theCreateSchemacut, somigrate --from-dumpemitted a script withoutCREATE SCHEMAthat failed when applied.identifyandmigrate --from-dumplist the schema facets that did not pair (cut only:anddump only:lines in the detail) when the dump matches no cut butdriftwould plan nothing against the nearest one. Thefrom-dump-no-matchhint no longer sends the user to adrift --dry-runthat reports 0 ops, and thefrom-dump-behindhint no longer says such a divergence was made outside the log; both offer the nearest cut by hand withmigrate --from.retrofit completion <shell>prints the shell’s completion script. The root’s help listed it, but running it was refused as an unknown command, and the scripts’ completion requests were refused the same way.- Every verb that loads the oplog refuses an operator this build does not
know, a
~tombstone included, naming the operator and the line, with the hint “upgrade the CLI to one that knows the operator; do not delete or edit the line”. Theopverbs andvalidaterefuse it asunknown-operator, except thatvalidatekeepssealed-oplog-unparsablefor one inside a sealed range. A renamed or split operator keeps its re-import or rename remedy. validatereports an unknown key once, naming its line, the key and the rules it leaves unverifiable, instead of oneunverifiablerow per rule for every op of the operator. No hint says to hand-fix the op. It also reports a header key this build does not know, and aretrofit-model-versionnewer than the build, as unverifiable (exit 2); an unsealed log with a newer model-version no longer passes.reconcileexits 2, not 1, for a file only a newer build can read: an unknown operator, a too-newretrofit-oplog-formatorretrofit-model-version, an unknown header, and (as before) an unknown key. The JSONcodetells them apart; a conflict marker, a repeated header or a refused fork stays exit 1.reconcile, theopverbs andop revise -irefuse an oplog that exists but cannot be read (for example mode 000) withoplog-unreadable(exit 2), printingcannot read oplogand the read error, instead ofoplog-not-found(“no oplog”) or a barecommand-error.validaterefuses an oplog holding an op that needs a newer engine version than itsengine-versionpin, withunsupported-on-engine-version(exit 2), asmigrateandsnapshotdo. A log whose pin was lowered by hand, as in a resolved header merge conflict, no longer passes the CI gate.op sealandidentifyrefuse the same log with the sameunsupported-on-engine-version(exit 2):op sealwrites no Seal over it andidentifyresolves no cut.op infodrops the note that said they did not check the engine-version gate.validate,migrateand the other commands that read the log refuse aCreatePartitioncarrying bothpartition_columnsandpartition_expression, as they already refused it onCreateTableandPartitionTable. Such a sub-partition used to replay, andmigraterendered its key from the expression alone, dropping the columns. ACreateTablecarrying both with nopartition_byis refused too, rather than read as a plain table with its key dropped.op addand the commands that read the log refuse anExtractTablethat moves every live column of a partitioned parent or a partition, withextract-partitioned-source. Such an extract ended the source while a parent’s partitions stayed live, or while a partition’s parent still named it, and the renderedDROPdiverged the database from the log.
Changed
op infoshows, for each rule, what retrofit does to an op that breaks it at two moments: OP ADD (op addand the other commands that write an op) and READING THE LOG (migrate,snapshot,validate,op seal,identify), eachrefusesorlets through.--as jsonkeepswhenand addsenforcement: {"append": ..., "replay": ...}, derived from it.op infosays READING THE LOGrefusesforpartition_cols_xor_expronCreateTable,CreatePartitionandPartitionTable, and forview-replace-supersetonReplaceView: the commands that read the log refuse an op that breaks them, whichop infosaid they let through.--as jsonreports these rules withwhen: append+replay,enforcement.replay: refusesandbinding: binding, no longerauthor-time-convenience.MergeTable’s rules listMergeShapeonsources: the refusal it already raised, when a source’s columns differ frominto’s or a source has a nextval default or no live column, now has a declaration.- Advisory messages end without a period. The second sentence of
data-motion-partition-not-pg-declarativeand ofmove-column-down-refused, the advice, moved to the hint. Amigrate --downscript’sreverse-unrecordedline ends; the database sits at <opid>. The five witness warnings on a record the down reverts state their cause instead of repeating their code, anddata-motion-source-consumed,dml-body-partition-dependent,data-only-single-statement-autocommitand the two drift-skip warnings are reworded in plain terms. data-loss-on-applymessages end without a period too: the reasonsop add,migrateandmigrate --downbuild for a drop, a cast, a join, an unjoin, a matview replace and a lookup extract or inline. Drift’s extract-recognition diagnostics read “an ExtractTable”.- The Advisory codes page shows every form of a message that varies with a field, each led by the value that selects it, and follows a message that is only a placeholder with its detail and hint.
Removed
LayerSeparationandNotSealedno longer appear in any operator’s rules: retrofit never checked either. Layer separation is a property of the oplog’s slots (docs/concepts.md, “The oplog”); the sealed region is guarded bysealed-region-write.
Log format
- Every oplog names its form on a fourth header line,
# retrofit-oplog-form: mergeor# retrofit-oplog-form: compact. A log without the line is refused (oplog-form-header-missing), as is any other value (unknown-oplog-form). In the merge form, whichschema createwrites unless--form compactis given, a removed or revised operation stays in the file as a~tombstone or behind areplaces=list;op compactdrops those traces. Every read checks each live Seal over its chain, so a Seal whose digest no longer matches is refused. For an existing log, add# retrofit-oplog-form: compactas line 4, after# engine-version:, then runretrofit schema form mergeto keep the history from then on. op show’s runnable framings printCreateIndex’skey_opclassesas--key_opclass, not--key_opclasse. The log’s bytes are unchanged; re-runop showfor any copied--key_opclasseline.- A
# retrofit-*header key this build does not know is refused on every verb that loads the oplog (unknown-key,writes-blocked-on-unknownsorrender-blocked-on-unknownsby the verb’s role), naming the key and its line, instead of being carried through opaquely. Run a build that knows the header; do not delete it. ExtractTabledeclares the ruleextract-partitioned-source: an extract that moves every live column refuses a partitioned parent or a partition as its source. A log holding such an extract no longer replays. Regenerate the log, runningDepartitionTableorDetachPartitionbefore the extract, or leave a parent’s partition key on it so the source survives.AttachPartitionends the partition’s own columns into its parent’s, andDetachPartitionmints the detached table’s columns and CHECKs afresh, recording their IDs in its new optionalcolumn-idsandadjacent-ids.SetDefault,DropDefault,AddNotNull,DropNotNullandSetColumnCommenttake an optionalpartition(partition=<table>, or the on-column spelled<partition>.<column>) that scopes the op to one partition of the column’s family. New refusals:partition-not-beneath-owner,partition-not-null-inherited,partition-identity-absent,detach-id-list-arityandattach-partition-check-mismatch. A log whose ops name an attached partition’s own columns, or a detached table’s columns by their earlier IDs, no longer replays: regenerate any log that holds anAttachPartition.