fix(orioledb): use alpha and beta conditionally based on AMI version (#51162)

## Problem

- Older orioledb projects show "Public Beta" instead of "Public Alpha"
in UI.
- List of backups in the "Scheduled backups" tab hangs.

## Solution

- show in the UI "Public Alpha" for projects older than
17.11.0.001-orioledb
- show in the UI "Public Beta" for new projects
- enable scheduled-backup query for "Public Beta"


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Backup availability messages now reflect OrioleDB’s current release
stage rather than always describing it as public beta.
* AWS backup queries are no longer disabled for every OrioleDB project;
they remain disabled during the alpha stage.
* **New Features**
* Added an informational notice and documentation link for scheduled
backups on AWS OrioleDB projects in alpha.
  * Added release-stage details to the PITR availability notice.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
This commit is contained in:
Artur ZakirovandCharis authored and GitHub committed 2026-10-02 09:43:32 -04:00
1 parent 895f016a54
commit 4b2d163a1d
7 files changed
+243 -7

No files matched your search

@@ -30,6 +30,7 @@ import {
useIsAwsK8sCloudProvider,
useIsHighAvailability,
useIsOrioleDb,
useOrioleDbReleaseStage,
useSelectedProjectQuery,
} from '@/hooks/misc/useSelectedProject'
import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants'
@@ -41,6 +42,7 @@ export const RestoreToNewProject = () => {
const { hasAccess: hasAccessToRestoreToNewProject, isLoading: isLoadingEntitlement } =
useCheckEntitlements('backup.restore_to_new_project')
const isOrioleDb = useIsOrioleDb()
const orioleDbReleaseStage = useOrioleDbReleaseStage()
const isAwsK8s = useIsAwsK8sCloudProvider()
const isHighAvailability = useIsHighAvailability()
@@ -130,7 +132,7 @@ export const RestoreToNewProject = () => {
<Admonition
type="default"
title="Restoring to new projects are not available for OrioleDB"
description="OrioleDB is currently in public beta and restoring to a new project is unavailable on OrioleDB projects"
description={`OrioleDB is currently in public ${orioleDbReleaseStage} and restoring to a new project is unavailable on OrioleDB projects`}
/>
)
}
+3 -2
View File
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import { databaseKeys } from './keys'
import type { components } from '@/data/api'
import { get, handleError } from '@/data/fetchers'
import { useIsOrioleDbInAws } from '@/hooks/misc/useSelectedProject'
import { useIsOrioleDbInAws, useOrioleDbReleaseStage } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
@@ -35,13 +35,14 @@ export const useBackupsQuery = <TData = BackupsData>(
) => {
// [Joshen] Check for specifically false to account for project not loaded yet
const isOrioleDbInAws = useIsOrioleDbInAws()
const orioleDbReleaseStage = useOrioleDbReleaseStage()
return useQuery<BackupsData, BackupsError, TData>({
queryKey: databaseKeys.backups(projectRef),
queryFn: ({ signal }) => getBackups({ projectRef }, signal),
enabled:
enabled &&
!isOrioleDbInAws &&
!(isOrioleDbInAws && orioleDbReleaseStage === 'alpha') &&
typeof projectRef !== 'undefined' &&
projectStatus !== PROJECT_STATUS.COMING_UP &&
projectStatus !== PROJECT_STATUS.UNKNOWN,
@@ -1,5 +1,10 @@
import { useParams } from 'common'
import {
isOrioleDbVersionAtLeast,
ORIOLEDB_PUBLIC_BETA_VERSION,
type OrioleDbReleaseStage,
} from './useSelectedProject.utils'
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
import { PROJECT_STATUS, PROVIDERS } from '@/lib/constants'
@@ -54,6 +59,16 @@ export const useIsOrioleDbInAws = () => {
return isOrioleDbInAws
}
// OrioleDB moved from Public Alpha to Public Beta at engine version 17.11.0.001.
// PITR remains unsupported for every OrioleDB version regardless of stage.
export const useOrioleDbReleaseStage = (): OrioleDbReleaseStage | undefined => {
const { data: project } = useSelectedProjectQuery()
if (!project?.dbVersion?.endsWith('orioledb')) return undefined
return isOrioleDbVersionAtLeast(project.dbVersion, ORIOLEDB_PUBLIC_BETA_VERSION)
? 'beta'
: 'alpha'
}
// [Joshen TODO] There's a duplicate method `resolveHighAvailability` in `useHighAvailability.constants`
export const useIsHighAvailability = () => {
const { data: project } = useSelectedProjectQuery()
@@ -0,0 +1,151 @@
import { describe, expect, it } from 'vitest'
import {
appVersionAtLeast,
compareAppVersions,
isOrioleDbVersionAtLeast,
parseAppVersionKey,
} from './useSelectedProject.utils'
describe('parseAppVersionKey', () => {
it('parses a dbVersion string into a version key', () => {
expect(parseAppVersionKey('supabase-postgres-17.9.9.999-orioledb')).toEqual([17, 9, 9, 999])
})
it('parses dbVersion strings with an arch prefix', () => {
expect(parseAppVersionKey('supabase-postgres-arm64-17.9.0.019')).toEqual([17, 9, 0, 19])
expect(parseAppVersionKey('supabase-postgres-x86_64-17.9.0.019')).toEqual([17, 9, 0, 19])
})
it('returns null for non-conforming strings', () => {
expect(parseAppVersionKey('CUSTOM_AMI_SENTINEL')).toBeNull()
expect(parseAppVersionKey('not-a-version')).toBeNull()
})
it('returns null for null or undefined input', () => {
expect(parseAppVersionKey(null)).toBeNull()
expect(parseAppVersionKey(undefined)).toBeNull()
})
})
describe('compareAppVersions', () => {
it('returns a negative number when a is older than b', () => {
expect(
compareAppVersions('supabase-postgres-17.8.9.999', 'supabase-postgres-17.9.0.019')
).toBeLessThan(0)
})
it('returns zero when versions are equal', () => {
expect(compareAppVersions('supabase-postgres-17.9.0.019', 'supabase-postgres-17.9.0.019')).toBe(
0
)
})
it('returns a positive number when a is newer than b', () => {
expect(
compareAppVersions('supabase-postgres-17.11.0.001', 'supabase-postgres-17.9.0.019')
).toBeGreaterThan(0)
})
it('compares numerically rather than by segment digit-width', () => {
// Regression case for the digit-concatenation bug in lib/helpers.ts'
// getSemanticVersion, where '15.1.1.2' (-> '15112') compares as less than
// '14.1.0.44' (-> '141044') despite being the newer version.
expect(
compareAppVersions('supabase-postgres-15.1.1.2', 'supabase-postgres-14.1.0.44')
).toBeGreaterThan(0)
})
it('throws on unparseable input', () => {
expect(() => compareAppVersions('not-a-version', 'supabase-postgres-17.9.0.019')).toThrow()
expect(() => compareAppVersions('supabase-postgres-17.9.0.019', 'not-a-version')).toThrow()
})
})
describe('appVersionAtLeast', () => {
it('returns true when the version is above or equal to the threshold', () => {
expect(appVersionAtLeast('supabase-postgres-17.9.9.999', 'supabase-postgres-17.9.0.019')).toBe(
true
)
expect(appVersionAtLeast('supabase-postgres-17.9.0.019', 'supabase-postgres-17.9.0.019')).toBe(
true
)
})
it('returns false when the version is below the threshold', () => {
expect(appVersionAtLeast('supabase-postgres-17.8.9.999', 'supabase-postgres-17.9.0.019')).toBe(
false
)
})
it('returns false for an unparseable version instead of throwing', () => {
expect(appVersionAtLeast('not-a-version', 'supabase-postgres-17.9.0.019')).toBe(false)
})
})
describe('isOrioleDbVersionAtLeast', () => {
it('returns true when the engine version is above the threshold', () => {
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.11.0.002-orioledb',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(true)
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.12.0.001-orioledb',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(true)
})
it('returns true when the engine version equals the threshold', () => {
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.11.0.001-orioledb',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(true)
})
it('returns false when the engine version is below the threshold', () => {
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.10.9.999-orioledb',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(false)
// wal-g (scheduled backup) support landed at 17.9.0.019, which is still
// below the Public Beta cutoff — these projects remain alpha.
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.9.0.019-orioledb',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(false)
})
it('returns false for a non-OrioleDB dbVersion', () => {
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.11.0.001',
'supabase-postgres-17.11.0.001-orioledb'
)
).toBe(false)
})
it('returns false for undefined dbVersion', () => {
expect(isOrioleDbVersionAtLeast(undefined, 'supabase-postgres-17.11.0.001-orioledb')).toBe(
false
)
})
it('returns false for a non-OrioleDB threshold', () => {
expect(
isOrioleDbVersionAtLeast(
'supabase-postgres-17.11.0.001-orioledb',
'supabase-postgres-17.11.0.001'
)
).toBe(false)
})
})
@@ -0,0 +1,42 @@
export type OrioleDbReleaseStage = 'alpha' | 'beta'
// OrioleDB engine versions ship as 4-part strings (e.g. `17.11.0.001`), which
// `lib/semver` doesn't parse (it's strict 3-part semver), so compare them separately here.
export const ORIOLEDB_PUBLIC_BETA_VERSION = 'supabase-postgres-17.11.0.001-orioledb'
// Sortable [major, minor, patch, build] key parsed from a dbVersion string like
// `supabase-postgres-17.11.0.001-orioledb` or `supabase-postgres-arm64-17.11.0.001`.
const APP_VERSION_KEY_RE = /^supabase-postgres-(?:arm64-|x86_64-)?(\d+)\.(\d+)\.(\d+)\.(\d+)/
export type AppVersionKey = [number, number, number, number]
// Returns null for non-conforming strings — callers should treat null as "not orderable".
export function parseAppVersionKey(version: string | null | undefined): AppVersionKey | null {
if (!version) return null
const match = APP_VERSION_KEY_RE.exec(version)
if (!match) return null
return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4])]
}
// Negative when `a` is older, zero when equal, positive when newer.
// Throws on unparseable input — null-tolerant callers parse first.
export function compareAppVersions(a: string, b: string): number {
const keyA = parseAppVersionKey(a)
const keyB = parseAppVersionKey(b)
if (!keyA) throw new Error(`unparseable app_version: ${a}`)
if (!keyB) throw new Error(`unparseable app_version: ${b}`)
return keyA[0] - keyB[0] || keyA[1] - keyB[1] || keyA[2] - keyB[2] || keyA[3] - keyB[3]
}
export function appVersionAtLeast(version: string, threshold: string): boolean {
if (!parseAppVersionKey(version)) return false
return compareAppVersions(version, threshold) >= 0
}
export const isOrioleDbVersionAtLeast = (dbVersion: string | undefined, threshold: string) => {
if (!dbVersion?.endsWith('orioledb') || !threshold.endsWith('orioledb')) return false
return appVersionAtLeast(dbVersion, threshold)
}
@@ -28,7 +28,11 @@ import { useBackupsQuery } from '@/data/database/backups-query'
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useHighAvailability } from '@/hooks/misc/useHighAvailability'
import { useIsOrioleDbInAws, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import {
useIsOrioleDbInAws,
useOrioleDbReleaseStage,
useSelectedProjectQuery,
} from '@/hooks/misc/useSelectedProject'
import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants'
import type { NextPageWithLayout } from '@/types'
@@ -71,6 +75,7 @@ const PITR = () => {
const { hasAccess: hasAccessToPitr, isLoading: isLoadingEntitlements } =
useCheckEntitlements('pitr.available_variants')
const isOrioleDbInAws = useIsOrioleDbInAws()
const orioleDbReleaseStage = useOrioleDbReleaseStage()
const {
data: backups,
error,
@@ -96,8 +101,8 @@ const PITR = () => {
return (
<Admonition
type="default"
title="Database backups are not available for OrioleDB"
description="OrioleDB is currently in public beta and Point-in-Time Recovery is unavailable on OrioleDB projects"
title="Point-in-Time Recovery is not available for OrioleDB"
description={`OrioleDB is currently in public ${orioleDbReleaseStage} and Point-in-Time Recovery is unavailable on OrioleDB projects`}
>
<DocsButton abbrev={false} className="mt-2" href={DOCS_URL} />
</Admonition>
@@ -1,6 +1,7 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { DatabaseBackup, Info } from 'lucide-react'
import { Admonition } from 'ui-patterns/Admonition'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
PageHeader,
@@ -17,12 +18,17 @@ import DatabaseBackupsNav from '@/components/interfaces/Database/Backups/Databas
import { DatabaseLayout } from '@/components/layouts/DatabaseLayout/DatabaseLayout'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import { AlertError } from '@/components/ui/AlertError'
import { DocsButton } from '@/components/ui/DocsButton'
import { HighAvailabilityDisabledEmptyState } from '@/components/ui/HighAvailability/HighAvailabilityDisabledEmptyState'
import InformationBox from '@/components/ui/InformationBox'
import { NoPermission } from '@/components/ui/NoPermission'
import { useBackupsQuery } from '@/data/database/backups-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useIsHighAvailability } from '@/hooks/misc/useSelectedProject'
import {
useIsHighAvailability,
useIsOrioleDbInAws,
useOrioleDbReleaseStage,
} from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
import type { NextPageWithLayout } from '@/types'
@@ -62,6 +68,8 @@ const ScheduledBackups = () => {
} = useBackupsQuery({ projectRef })
const isHighAvailability = useIsHighAvailability()
const isOrioleDbInAws = useIsOrioleDbInAws()
const orioleDbReleaseStage = useOrioleDbReleaseStage()
const isPitrEnabled = backups?.pitr_enabled
const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
@@ -69,6 +77,18 @@ const ScheduledBackups = () => {
'back_ups'
)
if (isOrioleDbInAws && orioleDbReleaseStage === 'alpha') {
return (
<Admonition
type="default"
title="Database backups are not available for OrioleDB"
description={`OrioleDB is currently in public ${orioleDbReleaseStage} and projects created are strictly ephemeral with no database backups`}
>
<DocsButton abbrev={false} className="mt-2" href={DOCS_URL} />
</Admonition>
)
}
if (isHighAvailability) {
return (
<HighAvailabilityDisabledEmptyState