Skip to content

Commit 259dc88

Browse files
committed
fix signups sequence
1 parent 073ba08 commit 259dc88

3 files changed

Lines changed: 86 additions & 4 deletions

File tree

src/where_the_plow/migrations/006_add_signup_name.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ def _has_column(conn: duckdb.DuckDBPyConnection, table: str, column: str) -> boo
2121

2222
def upgrade(conn: duckdb.DuckDBPyConnection) -> None:
2323
if not _has_column(conn, "signups", "name"):
24-
conn.execute("CREATE SEQUENCE IF NOT EXISTS signups_mig_seq")
24+
# Start the new sequence after the current max id so inserts don't
25+
# collide with migrated rows.
26+
max_id = conn.execute("SELECT coalesce(max(id), 0) FROM signups").fetchone()[0]
27+
start = max_id + 1
28+
29+
conn.execute(
30+
f"CREATE SEQUENCE IF NOT EXISTS signups_mig_seq START WITH {start}"
31+
)
2532
conn.execute("""
2633
CREATE TABLE signups_new (
2734
id BIGINT DEFAULT nextval('signups_mig_seq') PRIMARY KEY,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Fix signups auto-increment sequence.
2+
3+
Migration 006 recreated the signups table with a new sequence
4+
(signups_mig_seq) that started at 1 instead of after the max existing id.
5+
This caused primary key conflicts on new inserts.
6+
7+
Fix: recreate the table with a sequence seeded past max(id).
8+
Idempotent: only acts if the current sequence would produce a conflict.
9+
"""
10+
11+
import duckdb
12+
13+
14+
def _find_signups_sequence(conn: duckdb.DuckDBPyConnection) -> str | None:
15+
"""Return the name of the sequence used by signups.id, if any."""
16+
row = conn.execute(
17+
"SELECT column_default FROM information_schema.columns "
18+
"WHERE table_name='signups' AND column_name='id'"
19+
).fetchone()
20+
if not row or not row[0]:
21+
return None
22+
# Default looks like: nextval('signups_mig_seq')
23+
default = row[0]
24+
if "nextval" in default and "'" in default:
25+
return default.split("'")[1]
26+
return None
27+
28+
29+
def upgrade(conn: duckdb.DuckDBPyConnection) -> None:
30+
row = conn.execute("SELECT coalesce(max(id), 0) FROM signups").fetchone()
31+
max_id = row[0] if row else 0
32+
33+
if max_id == 0:
34+
# Empty table — nothing to fix.
35+
return
36+
37+
seq_name = _find_signups_sequence(conn)
38+
if not seq_name:
39+
return
40+
41+
# Check if the sequence is already past max_id by peeking at nextval.
42+
next_id = conn.execute(f"SELECT nextval('{seq_name}')").fetchone()[0]
43+
44+
if next_id > max_id:
45+
# Sequence is already ahead — no fix needed. But we consumed one
46+
# value with the nextval above; that's harmless (just a gap).
47+
return
48+
49+
# Need to recreate with a properly-seeded sequence.
50+
start = max_id + 1
51+
conn.execute(f"CREATE SEQUENCE signups_fix_seq START WITH {start}")
52+
conn.execute("""
53+
CREATE TABLE signups_fixed (
54+
id BIGINT DEFAULT nextval('signups_fix_seq') PRIMARY KEY,
55+
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
56+
name VARCHAR NOT NULL DEFAULT '',
57+
email VARCHAR NOT NULL,
58+
ip VARCHAR,
59+
user_agent VARCHAR,
60+
notify_plow BOOLEAN NOT NULL DEFAULT FALSE,
61+
notify_projects BOOLEAN NOT NULL DEFAULT FALSE,
62+
notify_siliconharbour BOOLEAN NOT NULL DEFAULT FALSE,
63+
note VARCHAR
64+
)
65+
""")
66+
conn.execute("""
67+
INSERT INTO signups_fixed
68+
(id, timestamp, name, email, ip, user_agent,
69+
notify_plow, notify_projects, notify_siliconharbour, note)
70+
SELECT id, timestamp, name, email, ip, user_agent,
71+
notify_plow, notify_projects, notify_siliconharbour, note
72+
FROM signups
73+
""")
74+
conn.execute("DROP TABLE signups")
75+
conn.execute("ALTER TABLE signups_fixed RENAME TO signups")

tests/test_migrate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def test_002_migrates_prod_db(tmp_path):
374374
)
375375
run_migrations(conn, migrations_dir)
376376

377-
assert get_version(conn) == 6
377+
assert get_version(conn) == 7
378378

379379
# Vehicles should have source column with composite PK
380380
veh_cols = {
@@ -496,6 +496,6 @@ def test_already_migrated_db_gets_stamped(tmp_path):
496496
)
497497
run_migrations(conn, migrations_dir)
498498

499-
# Should be stamped at version 6 with no errors
500-
assert get_version(conn) == 6
499+
# Should be stamped at version 7 with no errors
500+
assert get_version(conn) == 7
501501
conn.close()

0 commit comments

Comments
 (0)