Skip to content

Weasel 9.29.1

Choose a tag to compare

@jeremydmiller jeremydmiller released this 02 Sep 21:23
· 98 commits to master since this release
f4ae938

Three fixes. Two are silent false negatives — a schema that had drifted reported itself in sync — and the third is a model-corruption bug in the SQLite rebuild.

Read this before upgrading

Character column widths are compared now. On MySQL, SQL Server and Oracle, a varchar whose width differs between your model and the database produces an ALTER where it previously produced nothing — in both directions. A model narrower than an existing column now emits a narrowing ALTER, which can fail on real data. That is the correct contract, and it is what makes the downstream fix work, but it is worth checking your models against a production catalog before the first migration on 9.29.1.

PostgreSQL and SQLite are deliberately untouched by that change.

What changed

#550 — a widened character column is detected. For JasperFx/wolverine#4246. TableColumn.RawType() strips the parenthesised part of a type before comparing, on MySQL, SQL Server and Oracle alike. For most types that is right — MySQL 8 reports a bare INT for a column declared int(11), a DECIMAL carries a precision and a scale — and comparing those wholesale drifts on every schema check for tables nobody touched. Character and binary lengths are the exception: declared by the model, reported faithfully by every catalog, and load-bearing, because a column narrower than the value being written fails the insert. Widening a varchar in a model was invisible to the differ, so an existing database kept the old width forever.

A length is compared only for the types whose single parenthesised argument really is a character or byte count (CHAR, VARCHAR, NCHAR, NVARCHAR, CHARACTER, VARCHAR2, NVARCHAR2, BINARY, VARBINARY, RAW), and only when both sides state one — "cannot tell" never reports drift, so a model that declares a bare type keeps comparing the way it always has. varchar(max) is an unbounded sentinel, and Oracle's VARCHAR2(100 CHAR) and (100 BYTE) both read as 100.

Two Oracle defects in AlterColumnTypeSql surfaced with it, neither reachable before because a column type delta was almost never detected in the first place. It emitted the shape the column was moving from where the SQL Server and PostgreSQL twins emit the one it is moving to, so the statement altered the column to what it already was; and it restated the column's nullability, which Oracle rejects when it is not changing (ORA-01451, ORA-01442).

#548 — a view body is compared without normalizing away its string literals. SQL Server and SQLite normalized a view body by stripping every whitespace character and folding case across the whole string, literals included. Two views whose only difference was inside a literal compared equal: changing where name = 'active' to 'ACTIVE', or 'a b' to 'ab', both reported the schema as in sync. The view kept matching the old rows and the migration never ran.

Normalization now runs a scanner — Weasel.Core.ViewSqlNormalizer — that folds whitespace and case only outside string literals and copies literal contents verbatim. Delimited identifiers and comments are scanned as themselves, so the apostrophe in [Customer's Name], "o'brien" or -- don't cannot open a literal and invert inside/outside for the rest of the body. Everything outside a literal is behaviour-neutral, so no existing schema starts reporting a spurious migration.

#549 — a SQLite rebuild no longer mutates the model it was given. writeTableRecreation and its rollback twin built the throwaway replacement table out of the caller's own TableColumn instances, and Table.AddColumn(TableColumn) sets column.Parent = this — so generating a migration silently reparented a long-lived model's columns onto a temp table discarded a few lines later. No rebuild Weasel emits is affected; the damage lands afterwards, on the model that outlives the migration. Set StrictTypes on it and its columns consult the discarded parent, so the next diff reports drift on a column that already matches and the CREATE fails with unknown datatype for .... Each column is cloned instead, and the rebuild DDL is unchanged.

Still open

#546 is unchanged: on SQLite, resetting identity against a schema with no AUTOINCREMENT table fails with no such table: <schema>.sqlite_sequence.

Thanks to @jakobt for #548 and #549.