Skip to content

Commit 2ba3487

Browse files
committed
fix(tables): push-managed DBs never get the row-count triggers
Found by the copilot subagent's own answer (it had to count rows directly because the tables list read 0 everywhere): db:push knows nothing about the raw-SQL triggers versioned migrations install, so local + any push-managed DB had NO count triggers and row_count froze at 0. The dev migrate lane now applies the current definitions (verbatim from 0224/0241/0289) idempotently after push and reconciles stored counts (local: 237 tables corrected). Also drops the pre-0224 legacy row-level delete trigger still live on dev, which was double-decrementing deletes. Claude-Session: https://claude.ai/code/session_01CgaxNAaeD3taGdghbXn17w
1 parent ea588b6 commit 2ba3487

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

.github/workflows/migrations.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ jobs:
8181
exit 1
8282
fi
8383
bun run ./scripts/apply-dev-workspace-file-size-cutover.ts
84+
bun run ./scripts/apply-dev-table-triggers.ts
8485
else
8586
echo "Applying versioned migrations (db:migrate)"
8687
bun run ./scripts/migrate.ts
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { createLogger } from '@sim/logger'
2+
import postgres from 'postgres'
3+
4+
/**
5+
* Push-managed databases (local + dev use `db:push`) never receive the raw-SQL
6+
* row-count triggers that versioned migrations install on staging/prod — so every
7+
* table's `row_count` sat at 0 forever there (found live: the agent had to count rows
8+
* directly because the tables list lied). This applies the CURRENT trigger definitions
9+
* (verbatim from migrations 0224/0241/0289) idempotently, then reconciles the stored
10+
* counts with reality once. Runs in the dev migrate lane after `db:push`.
11+
*/
12+
const logger = createLogger('DevTableTriggers')
13+
14+
const url = process.env.MIGRATION_DATABASE_URL || process.env.DATABASE_URL
15+
if (!url) {
16+
throw new Error('Missing MIGRATION_DATABASE_URL or DATABASE_URL')
17+
}
18+
19+
const sql = postgres(url, {
20+
max: 1,
21+
connect_timeout: 10,
22+
max_lifetime: null,
23+
connection: { application_name: 'sim-dev-table-triggers' },
24+
})
25+
26+
const TRIGGER_SQL = `
27+
CREATE OR REPLACE FUNCTION increment_user_table_row_count_stmt()
28+
RETURNS TRIGGER AS $$
29+
BEGIN
30+
UPDATE user_table_definitions d
31+
SET row_count = d.row_count + c.n,
32+
updated_at = timezone('UTC', now())
33+
FROM (
34+
SELECT table_id, count(*)::int AS n
35+
FROM new_rows
36+
GROUP BY table_id
37+
) c
38+
WHERE d.id = c.table_id;
39+
40+
RETURN NULL;
41+
END;
42+
$$ LANGUAGE plpgsql;
43+
44+
CREATE OR REPLACE FUNCTION decrement_user_table_row_count_stmt()
45+
RETURNS TRIGGER AS $$
46+
BEGIN
47+
UPDATE user_table_definitions d
48+
SET row_count = GREATEST(d.row_count - c.n, 0),
49+
updated_at = timezone('UTC', now())
50+
FROM (
51+
SELECT table_id, count(*)::int AS n
52+
FROM old_rows
53+
GROUP BY table_id
54+
) c
55+
WHERE d.id = c.table_id;
56+
57+
RETURN NULL;
58+
END;
59+
$$ LANGUAGE plpgsql;
60+
61+
-- Legacy row-level triggers (pre-0224): coexisting with the stmt triggers they
62+
-- double-count — dev had the legacy delete trigger still installed, decrementing twice.
63+
DROP TRIGGER IF EXISTS user_table_rows_insert_trigger ON user_table_rows;
64+
DROP TRIGGER IF EXISTS user_table_rows_delete_trigger ON user_table_rows;
65+
66+
DROP TRIGGER IF EXISTS user_table_rows_insert_stmt_trigger ON user_table_rows;
67+
CREATE TRIGGER user_table_rows_insert_stmt_trigger
68+
AFTER INSERT ON user_table_rows
69+
REFERENCING NEW TABLE AS new_rows
70+
FOR EACH STATEMENT
71+
EXECUTE FUNCTION increment_user_table_row_count_stmt();
72+
73+
DROP TRIGGER IF EXISTS user_table_rows_delete_stmt_trigger ON user_table_rows;
74+
CREATE TRIGGER user_table_rows_delete_stmt_trigger
75+
AFTER DELETE ON user_table_rows
76+
REFERENCING OLD TABLE AS old_rows
77+
FOR EACH STATEMENT
78+
EXECUTE FUNCTION decrement_user_table_row_count_stmt();
79+
`
80+
81+
try {
82+
await sql.unsafe(TRIGGER_SQL)
83+
const reconciled = await sql`
84+
UPDATE user_table_definitions d
85+
SET row_count = actual.n
86+
FROM (
87+
SELECT d2.id, count(r.id)::int AS n
88+
FROM user_table_definitions d2
89+
LEFT JOIN user_table_rows r ON r.table_id = d2.id
90+
GROUP BY d2.id
91+
) actual
92+
WHERE actual.id = d.id AND d.row_count IS DISTINCT FROM actual.n
93+
RETURNING d.id
94+
`
95+
logger.info('Table row-count triggers applied; counts reconciled', {
96+
reconciledTables: reconciled.length,
97+
})
98+
} finally {
99+
await sql.end()
100+
}

0 commit comments

Comments
 (0)