Skip to content

Commit dbbeb36

Browse files
authored
fix(sqlite): avoid fresh install migration clash (#1360)
* fix(sqlite): avoid fresh install migration clash * fix(sqlite): rebuild recovered tables
1 parent c5b3ee1 commit dbbeb36

4 files changed

Lines changed: 192 additions & 14 deletions

File tree

src/main/presenter/sqlitePresenter/tables/baseTable.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ export abstract class BaseTable {
2727
return !!result
2828
}
2929

30+
protected getRecordedSchemaVersion(): number {
31+
const versionTable = this.db
32+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='schema_versions'`)
33+
.get() as { name: string } | undefined
34+
35+
if (!versionTable) {
36+
return 0
37+
}
38+
39+
const result = this.db.prepare('SELECT MAX(version) as version FROM schema_versions').get() as
40+
| { version: number | null }
41+
| undefined
42+
43+
return result?.version ?? 0
44+
}
45+
3046
// 执行表创建
3147
public createTable(): void {
3248
if (!this.tableExists()) {

src/main/presenter/sqlitePresenter/tables/deepchatSessions.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,48 @@ export class DeepChatSessionsTable extends BaseTable {
4242
}
4343

4444
getCreateTableSQL(): string {
45+
return this.getCreateTableSQLForVersion(0)
46+
}
47+
48+
override createTable(): void {
49+
if (this.tableExists()) {
50+
return
51+
}
52+
53+
this.db.exec(this.getCreateTableSQLForVersion(this.getRecordedSchemaVersion()))
54+
}
55+
56+
private getCreateTableSQLForVersion(version: number): string {
57+
const columns = [
58+
'id TEXT PRIMARY KEY',
59+
'provider_id TEXT NOT NULL',
60+
'model_id TEXT NOT NULL',
61+
"permission_mode TEXT NOT NULL DEFAULT 'full_access'"
62+
]
63+
64+
if (version >= 12) {
65+
columns.push(
66+
'system_prompt TEXT',
67+
'temperature REAL',
68+
'context_length INTEGER',
69+
'max_tokens INTEGER',
70+
'thinking_budget INTEGER',
71+
'reasoning_effort TEXT',
72+
'verbosity TEXT'
73+
)
74+
}
75+
76+
if (version >= 14) {
77+
columns.push(
78+
'summary_text TEXT',
79+
'summary_cursor_order_seq INTEGER NOT NULL DEFAULT 1',
80+
'summary_updated_at INTEGER'
81+
)
82+
}
83+
4584
return `
4685
CREATE TABLE IF NOT EXISTS deepchat_sessions (
47-
id TEXT PRIMARY KEY,
48-
provider_id TEXT NOT NULL,
49-
model_id TEXT NOT NULL,
50-
permission_mode TEXT NOT NULL DEFAULT 'full_access'
86+
${columns.join(',\n ')}
5187
);
5288
`
5389
}

src/main/presenter/sqlitePresenter/tables/newSessions.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,41 @@ export class NewSessionsTable extends BaseTable {
2020
}
2121

2222
getCreateTableSQL(): string {
23+
return this.getCreateTableSQLForVersion(0)
24+
}
25+
26+
override createTable(): void {
27+
if (this.tableExists()) {
28+
return
29+
}
30+
31+
this.db.exec(this.getCreateTableSQLForVersion(this.getRecordedSchemaVersion()))
32+
}
33+
34+
private getCreateTableSQLForVersion(version: number): string {
35+
const columns = [
36+
'id TEXT PRIMARY KEY',
37+
'agent_id TEXT NOT NULL',
38+
'title TEXT NOT NULL',
39+
'project_dir TEXT',
40+
'is_pinned INTEGER DEFAULT 0'
41+
]
42+
43+
if (version >= 11) {
44+
columns.push('is_draft INTEGER NOT NULL DEFAULT 0')
45+
}
46+
if (version >= 15) {
47+
columns.push("active_skills TEXT NOT NULL DEFAULT '[]'")
48+
}
49+
if (version >= 16) {
50+
columns.push("disabled_agent_tools TEXT NOT NULL DEFAULT '[]'")
51+
}
52+
53+
columns.push('created_at INTEGER NOT NULL', 'updated_at INTEGER NOT NULL')
54+
2355
return `
2456
CREATE TABLE IF NOT EXISTS new_sessions (
25-
id TEXT PRIMARY KEY,
26-
agent_id TEXT NOT NULL,
27-
title TEXT NOT NULL,
28-
project_dir TEXT,
29-
is_pinned INTEGER DEFAULT 0,
30-
is_draft INTEGER NOT NULL DEFAULT 0,
31-
active_skills TEXT NOT NULL DEFAULT '[]',
32-
disabled_agent_tools TEXT NOT NULL DEFAULT '[]',
33-
created_at INTEGER NOT NULL,
34-
updated_at INTEGER NOT NULL
57+
${columns.join(',\n ')}
3558
);
3659
CREATE INDEX IF NOT EXISTS idx_new_sessions_agent ON new_sessions(agent_id);
3760
CREATE INDEX IF NOT EXISTS idx_new_sessions_updated ON new_sessions(updated_at DESC);

test/main/presenter/sqlitePresenter.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,109 @@ describe('SQLitePresenter legacy schema bootstrap', () => {
125125
expect(columnNames.has('is_draft')).toBe(true)
126126
expect(columnNames.has('active_skills')).toBe(true)
127127
expect(columnNames.has('disabled_agent_tools')).toBe(true)
128+
129+
const versions = checkDb
130+
.prepare('SELECT version FROM schema_versions ORDER BY version ASC')
131+
.all() as Array<{ version: number }>
132+
expect(versions.map((row) => row.version)).toEqual(expect.arrayContaining([11, 15, 16]))
133+
checkDb.close()
134+
})
135+
136+
it('recreates new_sessions with applied columns when schema version is already 16', async () => {
137+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-sqlite-presenter-'))
138+
tempDirs.push(tempDir)
139+
140+
const dbPath = path.join(tempDir, 'agent.db')
141+
const bootstrapDb = new Database(dbPath)
142+
bootstrapDb.exec(`
143+
CREATE TABLE IF NOT EXISTS schema_versions (
144+
version INTEGER PRIMARY KEY,
145+
applied_at INTEGER NOT NULL
146+
);
147+
INSERT INTO schema_versions (version, applied_at) VALUES (16, ${Date.now()});
148+
`)
149+
bootstrapDb.close()
150+
151+
const presenter = new SQLitePresenter(dbPath)
152+
presenter.newSessionsTable.create('session-1', 'agent-1', 'Recovered session', null)
153+
presenter.close()
154+
155+
const checkDb = new Database(dbPath)
156+
const newSessionColumns = checkDb.prepare('PRAGMA table_info(new_sessions)').all() as Array<{
157+
name: string
158+
}>
159+
const columnNames = new Set(newSessionColumns.map((column) => column.name))
160+
161+
expect(columnNames.has('is_draft')).toBe(true)
162+
expect(columnNames.has('active_skills')).toBe(true)
163+
expect(columnNames.has('disabled_agent_tools')).toBe(true)
164+
165+
const row = checkDb
166+
.prepare(
167+
'SELECT is_draft, active_skills, disabled_agent_tools FROM new_sessions WHERE id = ?'
168+
)
169+
.get('session-1') as
170+
| {
171+
is_draft: number
172+
active_skills: string
173+
disabled_agent_tools: string
174+
}
175+
| undefined
176+
177+
expect(row).toEqual({
178+
is_draft: 0,
179+
active_skills: '[]',
180+
disabled_agent_tools: '[]'
181+
})
182+
checkDb.close()
183+
})
184+
185+
it('recreates deepchat_sessions with applied columns when schema version is already 14', async () => {
186+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-sqlite-presenter-'))
187+
tempDirs.push(tempDir)
188+
189+
const dbPath = path.join(tempDir, 'agent.db')
190+
const bootstrapDb = new Database(dbPath)
191+
bootstrapDb.exec(`
192+
CREATE TABLE IF NOT EXISTS schema_versions (
193+
version INTEGER PRIMARY KEY,
194+
applied_at INTEGER NOT NULL
195+
);
196+
INSERT INTO schema_versions (version, applied_at) VALUES (14, ${Date.now()});
197+
`)
198+
bootstrapDb.close()
199+
200+
const presenter = new SQLitePresenter(dbPath)
201+
presenter.deepchatSessionsTable.create('session-1', 'openai', 'gpt-4o')
202+
presenter.close()
203+
204+
const checkDb = new Database(dbPath)
205+
const deepchatColumns = checkDb.prepare('PRAGMA table_info(deepchat_sessions)').all() as Array<{
206+
name: string
207+
}>
208+
const columnNames = new Set(deepchatColumns.map((column) => column.name))
209+
210+
expect(columnNames.has('system_prompt')).toBe(true)
211+
expect(columnNames.has('summary_text')).toBe(true)
212+
expect(columnNames.has('summary_cursor_order_seq')).toBe(true)
213+
214+
const row = checkDb
215+
.prepare(
216+
'SELECT system_prompt, summary_text, summary_cursor_order_seq FROM deepchat_sessions WHERE id = ?'
217+
)
218+
.get('session-1') as
219+
| {
220+
system_prompt: string | null
221+
summary_text: string | null
222+
summary_cursor_order_seq: number
223+
}
224+
| undefined
225+
226+
expect(row).toEqual({
227+
system_prompt: null,
228+
summary_text: null,
229+
summary_cursor_order_seq: 1
230+
})
128231
checkDb.close()
129232
})
130233
})

0 commit comments

Comments
 (0)