Skip to content

Commit c7ea2da

Browse files
authored
fix: support orioledb migrations by removing concurrent index creation (#673)
* fix: support orioledb migrations by removing concurrent index creation * enable transformer to fix migrations for oriole db * remove version from docker compose to stop warning about it being obsolete/ignored * fix transformers folder naming
1 parent 9dc0996 commit c7ea2da

File tree

10 files changed

+104
-18
lines changed

10 files changed

+104
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
tenant_db:
3+
image: orioledb/orioledb:latest-pg17
4+
environment:
5+
POSTGRES_INITDB_ARGS: "--locale=C"
6+
volumes:
7+
- ./.docker/init-oriole-db:/docker-entrypoint-initdb.d

.docker/docker-compose-infra.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# docker-compose.yml
22

3-
version: '3'
43
services:
54

65
tenant_db:

.docker/docker-compose-monitoring.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: '3'
21
services:
32
pg_bouncer_exporter:
43
image: spreaker/prometheus-pgbouncer-exporter
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Enable oriole extension and set it as the default for table creation
2+
-- Without running this script oriole db defaults to creating heap tables
3+
CREATE EXTENSION IF NOT EXISTS orioledb;
4+
ALTER DATABASE postgres SET default_table_access_method = 'orioledb';

.github/workflows/ci.yml

+11-1
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,18 @@ jobs:
8282
S3_PROTOCOL_ACCESS_KEY_ID: ${{ secrets.TENANT_ID }}
8383
S3_PROTOCOL_ACCESS_KEY_SECRET: ${{ secrets.SERVICE_KEY }}
8484

85-
8685
- name: Upload coverage results to Coveralls
8786
uses: coverallsapp/github-action@master
8887
with:
8988
github-token: ${{ secrets.GITHUB_TOKEN }}
89+
90+
- name: Ensure OrioleDB migration compatibility
91+
run: |
92+
npm run infra:restart:oriole
93+
env:
94+
PGRST_JWT_SECRET: ${{ secrets.PGRST_JWT_SECRET }}
95+
DATABASE_URL: postgresql://postgres:[email protected]/postgres
96+
DB_INSTALL_ROLES: true
97+
ENABLE_DEFAULT_METRICS: false
98+
PG_QUEUE_ENABLE: false
99+
MULTI_TENANT: false

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"eslint:check": "eslint 'src/**'",
1919
"infra:stop": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml down --remove-orphans",
2020
"infra:start": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml up -d && sleep 5 && npm run migration:run",
21-
"infra:restart": "npm run infra:stop && npm run infra:start"
21+
"infra:start:oriole": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml -f ./.docker/docker-compose-infra-oriole-override.yml up -d && sleep 5 && npm run migration:run",
22+
"infra:restart": "npm run infra:stop && npm run infra:start",
23+
"infra:restart:oriole": "npm run infra:stop && npm run infra:start:oriole"
2224
},
2325
"author": "Supabase",
2426
"license": "ISC",

src/internal/database/migrations/migrate.ts

+47-14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ResetMigrationsOnTenant, RunMigrationsOnTenants } from '@storage/events
1414
import { ERRORS } from '@internal/errors'
1515
import { DBMigration } from './types'
1616
import { getSslSettings } from '../util'
17+
import { MigrationTransformer, DisableConcurrentIndexTransformer } from './transformers'
1718

1819
const {
1920
multitenantDatabaseUrl,
@@ -224,7 +225,7 @@ export async function obtainLockOnMultitenantDB<T>(fn: () => Promise<T>) {
224225
} finally {
225226
try {
226227
await multitenantKnex.raw(`SELECT pg_advisory_unlock(?);`, ['-8575985245963000605'])
227-
} catch (e) {}
228+
} catch {}
228229
}
229230
}
230231

@@ -550,12 +551,15 @@ export async function migrate({
550551
shouldCreateStorageSchema,
551552
upToMigration,
552553
}: MigrateOptions): Promise<Array<Migration>> {
554+
const accessMethod = await getDefaultAccessMethod(client)
553555
return withAdvisoryLock(
554556
waitForLock,
555557
runMigrations({
556558
migrationsDirectory,
557559
shouldCreateStorageSchema,
558560
upToMigration,
561+
// Remove concurrent index creation if we're using oriole db as it does not support it currently
562+
transformers: accessMethod === 'orioledb' ? [new DisableConcurrentIndexTransformer()] : [],
559563
})
560564
)(client)
561565
}
@@ -564,6 +568,7 @@ interface RunMigrationOptions {
564568
migrationsDirectory: string
565569
shouldCreateStorageSchema?: boolean
566570
upToMigration?: keyof typeof DBMigration
571+
transformers?: MigrationTransformer[]
567572
}
568573

569574
/**
@@ -576,6 +581,7 @@ function runMigrations({
576581
migrationsDirectory,
577582
shouldCreateStorageSchema,
578583
upToMigration,
584+
transformers = [],
579585
}: RunMigrationOptions) {
580586
return async (client: BasicPgClient) => {
581587
let intendedMigrations = await loadMigrationFilesCached(migrationsDirectory)
@@ -648,14 +654,17 @@ function runMigrations({
648654
}
649655

650656
for (const migration of migrationsToRun) {
651-
const result = await runMigration(migrationTableName, client)(migration)
657+
const result = await runMigration(
658+
migrationTableName,
659+
client
660+
)(runMigrationTransformers(migration, transformers))
652661
completedMigrations.push(result)
653662
}
654663

655664
return completedMigrations
656-
} catch (e: any) {
657-
const error: MigrationError = new Error(`Migration failed. Reason: ${e.message}`)
658-
error.cause = e
665+
} catch (e) {
666+
const error: MigrationError = new Error(`Migration failed. Reason: ${(e as Error).message}`)
667+
error.cause = e + ''
659668
throw error
660669
}
661670
}
@@ -675,6 +684,30 @@ function filterMigrations(
675684
return migrations.filter(notAppliedMigration)
676685
}
677686

687+
/**
688+
* Transforms provided migration by running all transformers
689+
* @param migration
690+
* @param transformers
691+
*/
692+
function runMigrationTransformers(
693+
migration: Migration,
694+
transformers: MigrationTransformer[]
695+
): Migration {
696+
for (const transformer of transformers) {
697+
migration = transformer.transform(migration)
698+
}
699+
return migration
700+
}
701+
702+
/**
703+
* Get the current default access method for this database
704+
* @param client
705+
*/
706+
async function getDefaultAccessMethod(client: BasicPgClient): Promise<string> {
707+
const result = await client.query(`SHOW default_table_access_method`)
708+
return result.rows?.[0]?.default_table_access_method || ''
709+
}
710+
678711
/**
679712
* Checks if a table exists
680713
* @param client
@@ -755,7 +788,7 @@ function withAdvisoryLock<T>(
755788
} finally {
756789
try {
757790
await client.query('SELECT pg_advisory_unlock(-8525285245963000605);')
758-
} catch (e) {}
791+
} catch {}
759792
}
760793
}
761794
}
@@ -828,11 +861,11 @@ async function refreshMigrationPosition(
828861
migrations.push(intendedMigrations[migration.index])
829862

830863
// add the other run migrations by updating their id and hash
831-
const afterMigration = newMigrations.slice(migration.index).map((m) => {
832-
;(m as any).id = m.id + 1
833-
;(m as any).hash = intendedMigrations[m.id].hash
834-
return m
835-
})
864+
const afterMigration = newMigrations.slice(migration.index).map((m) => ({
865+
...m,
866+
id: m.id + 1,
867+
hash: intendedMigrations[m.id].hash,
868+
}))
836869

837870
migrations.push(...afterMigration)
838871
newMigrations = migrations
@@ -868,18 +901,18 @@ async function refreshMigrationPosition(
868901
* Memoizes a promise
869902
* @param func
870903
*/
871-
function memoizePromise<T, Args extends any[]>(
904+
function memoizePromise<T, Args extends unknown[]>(
872905
func: (...args: Args) => Promise<T>
873906
): (...args: Args) => Promise<T> {
874907
const cache = new Map<string, Promise<T>>()
875908

876909
function generateKey(args: Args): string {
877910
return args
878911
.map((arg) => {
879-
if (typeof arg === 'object') {
912+
if (typeof arg === 'object' && arg !== null) {
880913
return Object.entries(arg).sort().toString()
881914
}
882-
return arg.toString()
915+
return String(arg)
883916
})
884917
.join('|')
885918
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Migration } from 'postgres-migrations/dist/types'
2+
import { MigrationTransformer } from './transformer'
3+
4+
const CONCURRENT_INDEX_FIND = 'INDEX CONCURRENTLY'
5+
const CONCURRENT_INDEX_REPLACE = 'INDEX'
6+
const DISABLE_TRANSACTION_STRING = '-- postgres-migrations disable-transaction'
7+
8+
export class DisableConcurrentIndexTransformer implements MigrationTransformer {
9+
transform(migration: Migration): Migration {
10+
if (!migration.sql.includes(CONCURRENT_INDEX_FIND)) {
11+
return migration
12+
}
13+
14+
return {
15+
...migration,
16+
// strip concurrently, and remove disable-transaction in sql and contents
17+
sql: migration.sql
18+
.replaceAll(CONCURRENT_INDEX_FIND, CONCURRENT_INDEX_REPLACE)
19+
.replace(DISABLE_TRANSACTION_STRING, ''),
20+
contents: migration.contents
21+
.replaceAll(CONCURRENT_INDEX_FIND, CONCURRENT_INDEX_REPLACE)
22+
.replace(DISABLE_TRANSACTION_STRING, ''),
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './transformer'
2+
export * from './disable-concurrent-index-transformer'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Migration } from 'postgres-migrations/dist/types'
2+
3+
export interface MigrationTransformer {
4+
transform(input: Migration): Migration
5+
}

0 commit comments

Comments
 (0)