migrations.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { source } from 'common-tags'
  2. import { executeQuery } from './query'
  3. import { PgMetaDatabaseError, WrappedResult } from './types'
  4. import { assertSelfHosted } from './util'
  5. import { makeRandomString } from '@/lib/helpers'
  6. export type ListMigrationsResult = {
  7. version: string
  8. name?: string
  9. }
  10. const listMigrationVersionsQuery = () =>
  11. 'select version, name from briven_migrations.schema_migrations order by version'
  12. const initializeHistoryTableQuery = () => `begin;
  13. create schema if not exists briven_migrations;
  14. create table if not exists briven_migrations.schema_migrations (version text not null primary key);
  15. alter table briven_migrations.schema_migrations add column if not exists statements text[];
  16. alter table briven_migrations.schema_migrations add column if not exists name text;
  17. commit;`
  18. const applyAndTrackMigrationsQuery = (query: string, name?: string) => {
  19. // Escapes literals using postgres dollar quoted string
  20. const dollar = `$${makeRandomString(20)}$`
  21. const quote = (s?: string) => (s ? dollar + s + dollar : `''`)
  22. return source`
  23. begin;
  24. -- apply sql from post body
  25. ${query};
  26. -- track statements in history table
  27. insert into briven_migrations.schema_migrations (version, name, statements)
  28. values (
  29. to_char(current_timestamp, 'YYYYMMDDHH24MISS'),
  30. ${quote(name)},
  31. array[${quote(query)}]
  32. );
  33. commit;
  34. `
  35. }
  36. export type ListMigrationVersionsOptions = {
  37. headers?: HeadersInit
  38. }
  39. /**
  40. * Lists all migrations in the migrations history table.
  41. *
  42. * _Only call this from server-side self-hosted code._
  43. */
  44. export async function listMigrationVersions({
  45. headers,
  46. }: ListMigrationVersionsOptions): Promise<WrappedResult<ListMigrationsResult[]>> {
  47. assertSelfHosted()
  48. const { data, error } = await executeQuery<ListMigrationsResult>({
  49. query: listMigrationVersionsQuery(),
  50. headers,
  51. })
  52. if (error) {
  53. // Return empty list if the migrations table doesn't exist
  54. if (error instanceof PgMetaDatabaseError && error.code === '42P01') {
  55. return { data: [], error: undefined }
  56. }
  57. return { data: undefined, error }
  58. }
  59. return { data, error: undefined }
  60. }
  61. export type ApplyAndTrackMigrationsOptions = {
  62. query: string
  63. name?: string
  64. headers?: HeadersInit
  65. }
  66. /**
  67. * Applies a SQL migration and tracks it in the migrations history table.
  68. *
  69. * _Only call this from server-side self-hosted code._
  70. */
  71. export async function applyAndTrackMigrations<T = unknown>({
  72. query,
  73. name,
  74. headers,
  75. }: ApplyAndTrackMigrationsOptions): Promise<WrappedResult<T[]>> {
  76. assertSelfHosted()
  77. const initializeResponse = await executeQuery<void>({
  78. query: initializeHistoryTableQuery(),
  79. headers,
  80. })
  81. if (initializeResponse.error) {
  82. return initializeResponse
  83. }
  84. const applyAndTrackResponse = await executeQuery<T>({
  85. query: applyAndTrackMigrationsQuery(query, name),
  86. headers,
  87. })
  88. return applyAndTrackResponse
  89. }