Public: anyone can read this; the crew can change it.

Drop the flow_only column and the flow_only_watch table: nothing reads them since d34910d4 #8770

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

torque/forge:torque/8731-drop-the-flow-only-column-and-the-flow-o → main 1 commit fork

  • Merged. torque/8731-drop-the-flow-only-column-and-the-flow-o was merged into main as 77e397c.

Review

Sign in to review this change.
4 files changed +78 −1
src/core/migration-pins/20261103100000-drop-flow-only.pin added +1
@@ -0,0 +1,1 @@
1 c46ce2de352c79513ff7d8497ced7c323e849c218916920355c901ed89ae8dff
src/core/migrations/20261103100000-drop-flow-only.ts added +9
@@ -0,0 +1,9 @@
1 // The FLOW ONLY switch left in d34910d4 (#7771), first shipped in v0.7.302;
2 // nothing since reads `repo.flow_only` or `flow_only_watch` (#8731). Its old
3 // `flow_only.cleared` events stay as they are, readable in a trail.
4 // Not additive, on purpose: test/schema-additive.test.ts names it as retired
5 // and holds the tree to never naming either again.
6 export default `
7 DROP TABLE IF EXISTS flow_only_watch;
8 ALTER TABLE repo DROP COLUMN flow_only;
9 `;
test/schema-additive.test.ts +38 −1
@@ -5,7 +5,8 @@
55 * read, and never adds a NOT NULL column they would not fill.
66 */
77 import { describe, expect, test } from "bun:test";
8 import { existsSync } from "node:fs";
8 import { Glob } from "bun";
9 import { existsSync, readFileSync } from "node:fs";
910 import { join } from "node:path";
1011 import releases from "../src/core/releases-before-tags.json" with { type: "json" };
1112 import {
@@ -70,11 +71,33 @@ describe("migrations are additive across a roll", () => {
7071 expect(inPlay.length).toBeGreaterThan(0);
7172 const offenders = migrationFiles
7273 .filter((m) => inPlay.some((ids) => !ids.has(m.name)))
74 .filter((m) => !(m.name in RETIRED))
7375 .map((m) => ({ name: m.name, problems: notAdditive(m.sql) }))
7476 .filter((m) => m.problems.length > 0);
7577 expect(offenders).toEqual([]);
7678 });
7779
80 test("a retired drop removes only what no file in src still names", () => {
81 // The release that stopped reading a table or column has rolled out
82 // (each entry says which); what keeps it true is that nothing since
83 // names the dropped thing again, outside the migrations that made it.
84 const named: string[] = [];
85 for (const path of new Glob("**/*.ts").scanSync(src)) {
86 if (path.startsWith("core/migrations/")) continue;
87 const text = readFileSync(join(src, path), "utf8");
88 for (const [migration, { drops }] of Object.entries(RETIRED))
89 for (const name of drops)
90 // `name.` is an event kind (`flow_only.cleared`), not the schema.
91 if (new RegExp(`\\b${name}\\b(?!\\.)`).test(text)) named.push(`${path} names ${name} (${migration})`);
92 }
93 expect(named).toEqual([]);
94 for (const [migration, { drops }] of Object.entries(RETIRED)) {
95 const file = migrationFiles.find((m) => m.name === migration);
96 expect(file).toBeDefined();
97 for (const name of drops) expect(file!.sql).toContain(name);
98 }
99 });
100
78101 test("a release cut moves no branch: its record is a tag, not a file in the tree", () => {
79102 // The releases before #6642 stay frozen beside the code; one cut since
80103 // lives on its tag (release/shipped.ts). A check runs on the tree
@@ -117,6 +140,20 @@ describe("migrations are additive across a roll", () => {
117140 });
118141 });
119142
143 /**
144 * Migrations that drop on purpose, each after the release that stopped
145 * reading what it drops has rolled out: the migration, the names it drops,
146 * and where the readers left.
147 */
148 const RETIRED: Record<string, { readonly drops: readonly string[]; readonly since: string }> = {
149 "20261103100000-drop-flow-only": {
150 drops: ["flow_only_watch", "flow_only"],
151 since: "d34910d4 (#7771), shipped from v0.7.302 (#8731)",
152 },
153 };
154
155 const src = join(import.meta.dir, "../src");
156
120157 interface Release {
121158 readonly migrations: number;
122159 readonly ids?: readonly string[];
test/schema-migrations.test.ts +30
@@ -304,6 +304,36 @@ describe("statements", () => {
304304 });
305305 });
306306
307 describe("the retired FLOW ONLY switch leaves no schema behind (#8731)", () => {
308 const DROP = "20261103100000-drop-flow-only";
309 const columns = (db: Database, table: string): string[] =>
310 db.query<{ name: string }, []>(`SELECT name FROM pragma_table_info('${table}')`).all().map((c) => c.name);
311
312 test("a database that had the switch on loses the column and the table, and keeps its repositories", () => {
313 const path = fresh();
314 const old = openDb(path, migrationFiles.filter((m) => m.name !== DROP).map((m) => m.sql));
315 expect(columns(old, "repo")).toContain("flow_only");
316 expect(has(old, "flow_only_watch")).toBe(true);
317 old.exec("INSERT INTO user (id, username, email, password_hash) VALUES ('u1', 'ada', '[email protected]', 'x')");
318 old.exec("INSERT INTO repo (id, owner_id, name, flow_only) VALUES ('r1', 'u1', 'lab', 1)");
319 old.exec("INSERT INTO flow_only_watch (repo_id, alarm_at) VALUES ('r1', 1)");
320 old.close();
321
322 const db = openDb(path, migrations);
323 expect(columns(db, "repo")).not.toContain("flow_only");
324 expect(has(db, "flow_only_watch")).toBe(false);
325 expect(db.query("SELECT id, name FROM repo").all()).toEqual([{ id: "r1", name: "lab" }]);
326 db.close();
327 });
328
329 test("a fresh database never keeps either", () => {
330 const db = openDb(":memory:", migrations);
331 expect(columns(db, "repo")).not.toContain("flow_only");
332 expect(has(db, "flow_only_watch")).toBe(false);
333 db.close();
334 });
335 });
336
307337 describe("a migration is a file named by a sortable id (#1837)", () => {
308338 const source = join(import.meta.dir, "../src/core/migrations");
309339

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs