mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
feat(session-query): add SQLite full-text search
This commit is contained in:
127
packages/session-query/session-query-sqlite/src/schema.ts
Normal file
127
packages/session-query/session-query-sqlite/src/schema.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/** SQLite schema for the disposable session full-text read model. */
|
||||
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { mkdir } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
/** Current derived-index schema version. Incompatible versions reset in place. */
|
||||
export const SESSION_QUERY_SQLITE_SCHEMA_VERSION = 1
|
||||
|
||||
/** SQLite application id protecting unrelated databases from derived resets. */
|
||||
export const SESSION_QUERY_SQLITE_APPLICATION_ID = 0x44534851
|
||||
|
||||
/** Supported SQLite journal modes. */
|
||||
export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
|
||||
|
||||
/**
|
||||
* Open, validate, and initialize persistent and connection-local schemas.
|
||||
* @param path - dedicated derived-index path or `:memory:`.
|
||||
* @param journalMode - validated SQLite journal mode.
|
||||
* @returns initialized database handle owned by the search service.
|
||||
*/
|
||||
export async function openSearchDatabase(path: string, journalMode: JournalMode): Promise<DatabaseSync> {
|
||||
const actual = path === ':memory:' ? path : resolve(path)
|
||||
if (actual !== ':memory:') await mkdir(dirname(actual), { recursive: true, mode: 0o700 })
|
||||
const db = new DatabaseSync(actual)
|
||||
try {
|
||||
// journalMode is a validated closed union, not caller-controlled SQL.
|
||||
db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`)
|
||||
const { application_id: applicationId } = db.prepare('PRAGMA application_id').get() as { application_id: number }
|
||||
const { user_version: version } = db.prepare('PRAGMA user_version').get() as { user_version: number }
|
||||
const userTables = listUserTables(db)
|
||||
if (applicationId !== 0 && applicationId !== SESSION_QUERY_SQLITE_APPLICATION_ID) {
|
||||
throw new Error(`session-search database at "${actual}" belongs to another application`)
|
||||
}
|
||||
if (applicationId === 0 && userTables.length > 0) {
|
||||
throw new Error(`session-search database at "${actual}" is not an empty or recognized derived index`)
|
||||
}
|
||||
if (applicationId === SESSION_QUERY_SQLITE_APPLICATION_ID && version !== SESSION_QUERY_SQLITE_SCHEMA_VERSION) {
|
||||
resetDerivedSchema(db)
|
||||
}
|
||||
ensurePersistentSchema(db)
|
||||
ensureTemporarySchema(db)
|
||||
return db
|
||||
} catch (error: unknown) {
|
||||
db.close()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function listUserTables(db: DatabaseSync): string[] {
|
||||
const rows = db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
||||
).all() as Array<{ name: string }>
|
||||
return rows.map(row => row.name)
|
||||
}
|
||||
|
||||
function resetDerivedSchema(db: DatabaseSync): void {
|
||||
for (const name of listUserTables(db)) {
|
||||
db.exec(`DROP TABLE IF EXISTS ${quoteIdentifier(name)}`)
|
||||
}
|
||||
db.exec('PRAGMA user_version = 0')
|
||||
}
|
||||
|
||||
function ensurePersistentSchema(db: DatabaseSync): void {
|
||||
db.exec(`PRAGMA application_id = ${SESSION_QUERY_SQLITE_APPLICATION_ID}`)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS search_state (
|
||||
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
||||
global_generation INTEGER NOT NULL
|
||||
) STRICT
|
||||
`)
|
||||
db.exec('INSERT OR IGNORE INTO search_state (singleton, global_generation) VALUES (1, 0)')
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS persisted_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
version INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
cwd TEXT,
|
||||
parent_session TEXT,
|
||||
seed_length INTEGER,
|
||||
fingerprint TEXT NOT NULL,
|
||||
generation INTEGER NOT NULL
|
||||
) STRICT
|
||||
`)
|
||||
db.exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS persisted_docs USING fts5(
|
||||
text,
|
||||
session_id UNINDEXED,
|
||||
seq UNINDEXED,
|
||||
type UNINDEXED,
|
||||
time UNINDEXED,
|
||||
surface UNINDEXED,
|
||||
tokenize = 'unicode61'
|
||||
)
|
||||
`)
|
||||
db.exec(`PRAGMA user_version = ${SESSION_QUERY_SQLITE_SCHEMA_VERSION}`)
|
||||
}
|
||||
|
||||
function ensureTemporarySchema(db: DatabaseSync): void {
|
||||
db.exec(`
|
||||
CREATE TEMP TABLE IF NOT EXISTS live_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
version INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
cwd TEXT,
|
||||
parent_session TEXT,
|
||||
seed_length INTEGER,
|
||||
fingerprint TEXT NOT NULL,
|
||||
generation INTEGER NOT NULL
|
||||
) STRICT
|
||||
`)
|
||||
db.exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS temp.live_docs USING fts5(
|
||||
text,
|
||||
session_id UNINDEXED,
|
||||
seq UNINDEXED,
|
||||
type UNINDEXED,
|
||||
time UNINDEXED,
|
||||
surface UNINDEXED,
|
||||
tokenize = 'unicode61'
|
||||
)
|
||||
`)
|
||||
}
|
||||
|
||||
function quoteIdentifier(value: string): string {
|
||||
return `"${value.replaceAll('"', '""')}"`
|
||||
}
|
||||
Reference in New Issue
Block a user