forked from tobi/qmd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmigrate-schema.ts
More file actions
162 lines (140 loc) · 5.23 KB
/
migrate-schema.ts
File metadata and controls
162 lines (140 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env bun
/**
* Migrate documents table from collection_id to collection name
*
* This script updates the database schema to use collection names
* instead of collection_id foreign keys, preparing for YAML-based
* collection management.
*/
import { Database } from "bun:sqlite";
import { join } from "path";
import { homedir } from "os";
const c = {
reset: "\x1b[0m",
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
dim: "\x1b[2m",
};
const dbPath = join(homedir(), ".cache", "qmd", "index.sqlite");
console.log(`${c.cyan}Migrating database schema...${c.reset}\n`);
console.log(`Database: ${dbPath}\n`);
const db = new Database(dbPath);
try {
db.exec("BEGIN TRANSACTION");
// Step 1: Add collection column to documents
console.log(`${c.yellow}1. Adding 'collection' column to documents table...${c.reset}`);
db.exec(`ALTER TABLE documents ADD COLUMN collection TEXT`);
console.log(` ${c.green}✓${c.reset} Column added`);
// Step 2: Populate collection names from collections table
console.log(`\n${c.yellow}2. Populating collection names...${c.reset}`);
const result = db.exec(`
UPDATE documents
SET collection = (
SELECT name FROM collections WHERE collections.id = documents.collection_id
)
WHERE collection IS NULL
`);
console.log(` ${c.green}✓${c.reset} Updated ${result} rows`);
// Step 3: Verify no NULL values
const nullCount = db.query<{ count: number }, []>(
`SELECT COUNT(*) as count FROM documents WHERE collection IS NULL`
).get();
if (nullCount && nullCount.count > 0) {
throw new Error(`Found ${nullCount.count} documents with NULL collection names`);
}
console.log(` ${c.green}✓${c.reset} All documents have collection names`);
// Step 4: Create new documents table without collection_id
console.log(`\n${c.yellow}3. Creating new documents table...${c.reset}`);
db.exec(`
CREATE TABLE documents_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection TEXT NOT NULL,
path TEXT NOT NULL,
title TEXT NOT NULL,
hash TEXT NOT NULL,
created_at TEXT NOT NULL,
modified_at TEXT NOT NULL,
active INTEGER DEFAULT 1,
FOREIGN KEY (hash) REFERENCES content(hash) ON DELETE CASCADE,
UNIQUE(collection, path)
)
`);
console.log(` ${c.green}✓${c.reset} New table created`);
// Step 5: Copy data
console.log(`\n${c.yellow}4. Copying data to new table...${c.reset}`);
db.exec(`
INSERT INTO documents_new (id, collection, path, title, hash, created_at, modified_at, active)
SELECT id, collection, path, title, hash, created_at, modified_at, active
FROM documents
`);
const rowCount = db.query<{ count: number }, []>(
`SELECT COUNT(*) as count FROM documents_new`
).get();
console.log(` ${c.green}✓${c.reset} Copied ${rowCount?.count} documents`);
// Step 6: Drop old table and rename new one
console.log(`\n${c.yellow}5. Replacing old table...${c.reset}`);
db.exec(`DROP TABLE documents`);
db.exec(`ALTER TABLE documents_new RENAME TO documents`);
console.log(` ${c.green}✓${c.reset} Table replaced`);
// Step 7: Recreate indices
console.log(`\n${c.yellow}6. Recreating indices...${c.reset}`);
db.exec(`CREATE INDEX idx_documents_collection ON documents(collection, active)`);
db.exec(`CREATE INDEX idx_documents_hash ON documents(hash)`);
console.log(` ${c.green}✓${c.reset} Indices created`);
// Step 8: Update FTS trigger to use collection name
console.log(`\n${c.yellow}7. Updating FTS trigger...${c.reset}`);
db.exec(`DROP TRIGGER IF EXISTS documents_ai`);
db.exec(`
CREATE TRIGGER documents_ai AFTER INSERT ON documents
WHEN new.active = 1
BEGIN
INSERT INTO documents_fts(rowid, filepath, title, body)
SELECT
new.id,
new.collection || '/' || new.path,
new.title,
(SELECT doc FROM content WHERE hash = new.hash)
WHERE new.active = 1;
END
`);
db.exec(`DROP TRIGGER IF EXISTS documents_au`);
db.exec(`
CREATE TRIGGER documents_au AFTER UPDATE ON documents
BEGIN
-- Delete from FTS if no longer active
DELETE FROM documents_fts WHERE rowid = old.id AND new.active = 0;
-- Update FTS if still/newly active
INSERT OR REPLACE INTO documents_fts(rowid, filepath, title, body)
SELECT
new.id,
new.collection || '/' || new.path,
new.title,
(SELECT doc FROM content WHERE hash = new.hash)
WHERE new.active = 1;
END
`);
console.log(` ${c.green}✓${c.reset} Triggers updated`);
// Commit transaction
db.exec("COMMIT");
console.log(`\n${c.green}✓ Migration completed successfully!${c.reset}`);
// Show summary
const collections = db.query<{ collection: string; count: number }, []>(`
SELECT collection, COUNT(*) as count
FROM documents
WHERE active = 1
GROUP BY collection
ORDER BY collection
`).all();
console.log(`\n${c.dim}Documents by collection:${c.reset}`);
for (const coll of collections) {
console.log(` ${coll.collection}: ${coll.count} files`);
}
} catch (error) {
db.exec("ROLLBACK");
console.error(`\n${c.yellow}✗ Migration failed:${c.reset} ${error}`);
console.error(`${c.dim}Database rolled back to previous state${c.reset}`);
process.exit(1);
} finally {
db.close();
}