Skip to content

Commit 23040f4

Browse files
authored
Merge pull request supabase-community#4 from supabase-community/feat/delete-pre-16-dbs
feat: handle pre-16 pg versions (delete for now)
2 parents 372a4b3 + 92f5477 commit 23040f4

File tree

1 file changed

+121
-6
lines changed
  • apps/postgres-new/lib

1 file changed

+121
-6
lines changed

apps/postgres-new/lib/db.ts

Lines changed: 121 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export type Database = {
99
isHidden: boolean
1010
}
1111

12+
// Hardcoding to v16 (PGlite v0.2.0+) for now, in the future we should
13+
// dynamically check this somehow
14+
const currentPgVersion = '16'
1215
const prefix = 'playground'
1316

1417
let metaDbPromise: Promise<PGliteInterface> | undefined
@@ -20,6 +23,8 @@ export async function getMetaDb() {
2023
}
2124

2225
async function run() {
26+
await handleUnsupportedPGVersion('meta')
27+
2328
const metaDb = new PGlite(`idb://meta`, {
2429
extensions: {
2530
vector,
@@ -47,6 +52,7 @@ export async function getDb(id: string) {
4752

4853
async function run() {
4954
const metaDb = await getMetaDb()
55+
5056
const {
5157
rows: [database],
5258
} = await metaDb.query<Database>('select * from databases where id = $1', [id])
@@ -55,7 +61,11 @@ export async function getDb(id: string) {
5561
throw new Error(`Database with ID '${id}' doesn't exist`)
5662
}
5763

58-
const db = new PGlite(`idb://${prefix}-${id}`, {
64+
const dbPath = `${prefix}-${id}`
65+
66+
await handleUnsupportedPGVersion(dbPath)
67+
68+
const db = new PGlite(`idb://${dbPath}`, {
5969
extensions: {
6070
vector,
6171
},
@@ -88,19 +98,124 @@ export async function closeDb(id: string) {
8898
export async function deleteDb(id: string) {
8999
await closeDb(id)
90100

91-
// TODO: fix issue where PGlite holds on the IndexedDB preventing delete
101+
// TODO: fix issue where PGlite holds on to the IndexedDB preventing delete
92102
// Once fixed, turn this into an `await` so we can forward legitimate errors
93-
new Promise<void>((resolve, reject) => {
94-
const req = indexedDB.deleteDatabase(`/pglite/${prefix}-${id}`)
103+
deleteIndexedDb(`/pglite/${prefix}-${id}`)
104+
}
105+
106+
/**
107+
* Peeks into the files of an IndexedDB-backed PGlite database
108+
* and returns the Postgres version it was created under (via `./PG_VERSION`).
109+
*
110+
* Useful to detect version compatibility since it doesn't require instantiating
111+
* a PGlite instance.
112+
*/
113+
export async function getPGliteDBVersion(id: string) {
114+
const dbPath = `/pglite/${id}`
115+
const versionPath = `${dbPath}/PG_VERSION`
116+
117+
const dbs = await indexedDB.databases()
118+
const databaseExists = dbs.some((db) => db.name === dbPath)
119+
120+
if (!databaseExists) {
121+
return undefined
122+
}
123+
124+
try {
125+
return await new Promise<string>((resolve, reject) => {
126+
const req = indexedDB.open(dbPath)
127+
128+
req.onsuccess = async () => {
129+
const db = req.result
130+
131+
try {
132+
const transaction = db.transaction(['FILE_DATA'], 'readonly')
133+
const objectStore = transaction.objectStore('FILE_DATA')
134+
135+
const getReq: IDBRequest<{ contents: Int8Array }> = objectStore.get(versionPath)
136+
137+
getReq.onerror = () => {
138+
db.close()
139+
reject(
140+
getReq.error
141+
? `An error occurred when retrieving '${versionPath}' from IndexedDB database: ${getReq.error.message}`
142+
: `An unknown error occurred when retrieving '${versionPath}' from IndexedDB database`
143+
)
144+
}
145+
146+
getReq.onsuccess = () => {
147+
const decoder = new TextDecoder()
148+
const version = decoder.decode(getReq.result.contents).trim()
149+
db.close()
150+
resolve(version)
151+
}
152+
} catch (err) {
153+
db.close()
154+
reject(
155+
err && err instanceof Error
156+
? `An error occurred when opening 'FILE_DATA' object store from IndexedDB database: ${err.message}`
157+
: `An unknown error occurred when opening 'FILE_DATA' object store from IndexedDB database`
158+
)
159+
}
160+
}
161+
req.onerror = () => {
162+
reject(
163+
req.error
164+
? `An error occurred when opening IndexedDB database: ${req.error.message}`
165+
: 'An unknown error occurred when opening IndexedDB database'
166+
)
167+
}
168+
req.onblocked = () => {
169+
reject('IndexedDB database was blocked when opening')
170+
}
171+
})
172+
} catch (err) {
173+
// If the retrieval failed, the DB is corrupt or not initialized, return undefined
174+
return undefined
175+
}
176+
}
177+
178+
/**
179+
* Handles scenario where client had created DB with an old version of PGlite (likely 0.1.5, PG v15).
180+
* For now we'll simply delete and recreate it, which loses data (as 0.1.5 was only used before official release).
181+
*
182+
* In the future we need to come up with an upgrade strategy.
183+
*/
184+
export async function handleUnsupportedPGVersion(dbPath: string) {
185+
const dbs = await indexedDB.databases()
186+
const databaseExists = dbs.some((db) => db.name === `/pglite/${dbPath}`)
187+
188+
if (databaseExists) {
189+
const version = await getPGliteDBVersion(dbPath)
190+
191+
console.debug(`PG version of '${dbPath}' DB is ${version}`)
192+
193+
if (version !== currentPgVersion) {
194+
console.warn(
195+
`DB '${dbPath}' is on PG version ${version}, deleting and replacing with version ${currentPgVersion}`
196+
)
197+
198+
await deleteIndexedDb(`/pglite/${dbPath}`)
199+
}
200+
}
201+
}
202+
203+
export async function deleteIndexedDb(name: string) {
204+
await new Promise<void>((resolve, reject) => {
205+
const req = indexedDB.deleteDatabase(name)
95206

96207
req.onsuccess = () => {
97208
resolve()
98209
}
99210
req.onerror = () => {
100-
reject('An error occurred when deleted database')
211+
reject(
212+
req.error
213+
? `An error occurred when deleting IndexedDB database: ${req.error.message}`
214+
: 'An unknown error occurred when deleting IndexedDB database'
215+
)
101216
}
102217
req.onblocked = () => {
103-
reject('Database is blocked')
218+
reject('IndexedDB database was blocked when deleting')
104219
}
105220
})
106221
}

0 commit comments

Comments
 (0)