fix(sqlite): match reserved object names literally
This commit is contained in:
@@ -91,7 +91,7 @@ function configureDatabase(db: DatabaseSync, path: string, journalMode: JournalM
|
|||||||
const { user_version: onDisk } = db.prepare('PRAGMA user_version').get() as { user_version: number }
|
const { user_version: onDisk } = db.prepare('PRAGMA user_version').get() as { user_version: number }
|
||||||
const { application_id: applicationId } = db.prepare('PRAGMA application_id').get() as { application_id: number }
|
const { application_id: applicationId } = db.prepare('PRAGMA application_id').get() as { application_id: number }
|
||||||
const { count: userObjectCount } = db.prepare(
|
const { count: userObjectCount } = db.prepare(
|
||||||
"SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%'",
|
"SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*'",
|
||||||
).get() as { count: number }
|
).get() as { count: number }
|
||||||
if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) {
|
if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) {
|
||||||
throw new Error(`session database at "${path}" has an unversioned schema or application identity`)
|
throw new Error(`session database at "${path}" has an unversioned schema or application identity`)
|
||||||
|
|||||||
@@ -345,6 +345,23 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
|
|||||||
unchanged.close()
|
unchanged.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('counts a sqliteX table as user-owned instead of mistaking it for SQLite metadata', async () => {
|
||||||
|
const path = await freshDbPath()
|
||||||
|
const unrelated = new DatabaseSync(path)
|
||||||
|
unrelated.exec('CREATE TABLE sqliteX (value TEXT)')
|
||||||
|
unrelated.exec("INSERT INTO sqliteX VALUES ('safe')")
|
||||||
|
unrelated.close()
|
||||||
|
|
||||||
|
expect(() => openDatabase(path, 'wal')).toThrow(/unversioned schema or application identity/)
|
||||||
|
|
||||||
|
const unchanged = new DatabaseSync(path)
|
||||||
|
expect(unchanged.prepare('SELECT value FROM sqliteX').get()).toEqual({ value: 'safe' })
|
||||||
|
expect(unchanged.prepare('PRAGMA application_id').get()).toEqual({ application_id: 0 })
|
||||||
|
expect(unchanged.prepare('PRAGMA user_version').get()).toEqual({ user_version: 0 })
|
||||||
|
expect(unchanged.prepare('PRAGMA journal_mode').get()).toEqual({ journal_mode: 'delete' })
|
||||||
|
unchanged.close()
|
||||||
|
})
|
||||||
|
|
||||||
it('rejects view-only and foreign-application unversioned databases without mutation', async () => {
|
it('rejects view-only and foreign-application unversioned databases without mutation', async () => {
|
||||||
const viewPath = await freshDbPath()
|
const viewPath = await freshDbPath()
|
||||||
const viewOnly = new DatabaseSync(viewPath)
|
const viewOnly = new DatabaseSync(viewPath)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export async function openSearchDatabase(path: string, journalMode: JournalMode)
|
|||||||
|
|
||||||
function listUserTables(db: DatabaseSync): string[] {
|
function listUserTables(db: DatabaseSync): string[] {
|
||||||
const rows = db.prepare(
|
const rows = db.prepare(
|
||||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT GLOB 'sqlite_*' ORDER BY name",
|
||||||
).all() as Array<{ name: string }>
|
).all() as Array<{ name: string }>
|
||||||
return rows.map(row => row.name)
|
return rows.map(row => row.name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1148,6 +1148,26 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
|
|||||||
expect(stillForeign.prepare('PRAGMA journal_mode').get()).toEqual({ journal_mode: 'wal' })
|
expect(stillForeign.prepare('PRAGMA journal_mode').get()).toEqual({ journal_mode: 'wal' })
|
||||||
stillForeign.close()
|
stillForeign.close()
|
||||||
|
|
||||||
|
const wildcardPath = await temporaryPath('sqlite-wildcard.db')
|
||||||
|
const wildcard = new DatabaseSync(wildcardPath)
|
||||||
|
wildcard.exec('PRAGMA journal_mode = WAL')
|
||||||
|
wildcard.exec('CREATE TABLE sqliteX(value TEXT)')
|
||||||
|
wildcard.exec("INSERT INTO sqliteX VALUES ('safe')")
|
||||||
|
wildcard.close()
|
||||||
|
const wildcardCtx = new Context()
|
||||||
|
await wildcardCtx.plugin(SessionStore)
|
||||||
|
await expect(wildcardCtx.plugin(SessionQuerySqlite, {
|
||||||
|
path: wildcardPath,
|
||||||
|
journalMode: 'delete',
|
||||||
|
})).rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
|
||||||
|
expect(wildcardCtx.sessionQuery).toBeUndefined()
|
||||||
|
const stillWildcard = new DatabaseSync(wildcardPath)
|
||||||
|
expect(stillWildcard.prepare('SELECT value FROM sqliteX').get()).toEqual({ value: 'safe' })
|
||||||
|
expect(stillWildcard.prepare('PRAGMA application_id').get()).toEqual({ application_id: 0 })
|
||||||
|
expect(stillWildcard.prepare('PRAGMA user_version').get()).toEqual({ user_version: 0 })
|
||||||
|
expect(stillWildcard.prepare('PRAGMA journal_mode').get()).toEqual({ journal_mode: 'wal' })
|
||||||
|
stillWildcard.close()
|
||||||
|
|
||||||
const otherAppPath = await temporaryPath('other-app.db')
|
const otherAppPath = await temporaryPath('other-app.db')
|
||||||
const otherApp = new DatabaseSync(otherAppPath)
|
const otherApp = new DatabaseSync(otherAppPath)
|
||||||
otherApp.exec('PRAGMA application_id = 123')
|
otherApp.exec('PRAGMA application_id = 123')
|
||||||
|
|||||||
Reference in New Issue
Block a user