Collapse alembic 0001..0089 into one baseline #245
@@ -39,22 +39,31 @@ Four things still had to be added by hand, because they are not in the models:
|
||||
Extensions are database objects, not table metadata, so no model can carry
|
||||
them. `IF NOT EXISTS` because a re-run must not fail.
|
||||
|
||||
2. **The two settings singleton rows.** 0002 and 0003 did not only build
|
||||
schema — they each inserted a row:
|
||||
2. **Three seed inserts** — the two settings singletons (0002, 0003) and the
|
||||
three hygiene system tags (0075). Some migrations did not only build schema;
|
||||
they inserted rows the product needs in order to function, and nothing in
|
||||
the application ever creates them. Every consumer reads them with
|
||||
`scalar_one()`, which RAISES `NoResultFound` on an empty result rather than
|
||||
returning None, so their absence is a crash and not a degradation.
|
||||
|
||||
INSERT INTO import_settings (id) VALUES (1)
|
||||
INSERT INTO ml_settings (id) VALUES (1)
|
||||
Distinguishing these from the other data statements in the chain is the
|
||||
whole trick, and the rule turns out to be mechanical:
|
||||
|
||||
and nothing in the application ever creates them. `ImportSettings.load()`
|
||||
and `MLSettings.load()` are `select(...).scalar_one()`, which RAISES
|
||||
`NoResultFound` on an empty result rather than returning None. So a baseline
|
||||
built from models alone would produce those two tables empty and crash a
|
||||
fresh install on its first settings access.
|
||||
* `INSERT ... VALUES (...)` with literal values is a SEED. It creates
|
||||
something the product ships. It must be carried.
|
||||
* `INSERT ... SELECT ... FROM <table>` is a BACKFILL. It derives rows
|
||||
from rows that already exist, so on an empty database it inserts
|
||||
nothing and carrying it would be pointless. 0034 (artist_visit), 0040
|
||||
and 0047 (series_chapter) are all of this shape and are correctly
|
||||
absent here.
|
||||
|
||||
This is worth dwelling on, because it is invisible to every automated check
|
||||
this project has: `baseline.yml` compares SCHEMA, and the schema would have
|
||||
been a perfect match. Only running the application against a fresh database
|
||||
finds it.
|
||||
This category is invisible to every automated check this project has:
|
||||
`baseline.yml` compares SCHEMA, and a baseline missing all three seeds still
|
||||
produces a byte-identical schema and a perfectly green diff. What caught the
|
||||
system tags was the integration suite — 36 tests failing on
|
||||
`NoResultFound` — after a first version of this file shipped with only the
|
||||
two settings rows. A first-run check against the real application is the
|
||||
only thing that finds this class of defect.
|
||||
|
||||
3. **The `pgvector` import.** Autogenerate emits qualified
|
||||
`pgvector.sqlalchemy.vector.VECTOR(...)` references without importing the
|
||||
@@ -794,6 +803,26 @@ def upgrade() -> None:
|
||||
op.execute("INSERT INTO import_settings (id) VALUES (1)")
|
||||
op.execute("INSERT INTO ml_settings (id) VALUES (1)")
|
||||
|
||||
# The three hygiene system tags, from 0075. These are PRODUCT data, not
|
||||
# operator configuration — 0075's own docstring says so: "the fix keys on
|
||||
# SYSTEM tags the product ships". The presentation and process auto-apply
|
||||
# sweeps look them up with scalar_one(), so without these rows those
|
||||
# features raise NoResultFound rather than degrading.
|
||||
#
|
||||
# 0075 adopted an existing same-name general tag before inserting, because
|
||||
# an operator might already have tagged `wip` by hand. That cannot happen
|
||||
# on the empty database this file runs against, but the guard is kept: it
|
||||
# costs nothing and makes the statement safe to re-run.
|
||||
for _name in ("wip", "banner", "editor screenshot"):
|
||||
op.execute(
|
||||
sa.text(
|
||||
"INSERT INTO tag (name, kind, is_system) "
|
||||
"SELECT :name, 'general', true WHERE NOT EXISTS ("
|
||||
" SELECT 1 FROM tag WHERE lower(name) = lower(:name)"
|
||||
")"
|
||||
).bindparams(name=_name)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Deliberately not implemented.
|
||||
|
||||
Reference in New Issue
Block a user