diff --git a/frontend/src/lib/components/DBManagerDrawer.svelte b/frontend/src/lib/components/DBManagerDrawer.svelte
index 330449eb7e..71dda5e1d3 100644
--- a/frontend/src/lib/components/DBManagerDrawer.svelte
+++ b/frontend/src/lib/components/DBManagerDrawer.svelte
@@ -17,6 +17,7 @@
import {
ADMIN_DATATABLE_ROLE,
datatableNameTakesRole,
+ defaultMigrationRole,
type DatatableRowAction
} from './dbTypes'
import ResourcePicker from './ResourcePicker.svelte'
@@ -103,6 +104,17 @@
if (effective) untrack(() => (uriState.selectedRole = effective))
})
+ const contentInput = $derived.by(() => {
+ const input = uriState.effectiveInput
+ if (input?.type !== 'database' || selectedDatatable === undefined) return input
+ const migrationRole = defaultMigrationRole(
+ selectedDatatable,
+ rolesOfCurrent?.permissioned,
+ rolesOfCurrent?.default_role
+ )
+ return migrationRole === undefined ? input : { ...input, migrationRole }
+ })
+
// Every data table with its schemas and tables, in one call: this is what the
// left pane's tree navigates, so it has to cover the data tables the user is
// not currently on, not just the selected one. The privileges it reports are
@@ -295,11 +307,11 @@
noPadding
id="db-manager-drawer"
>
- {#if uriState.effectiveInput && ws && roleSettled}
+ {#if contentInput && ws && roleSettled}
{#key `${selectedDatatable}~${selectedRole ?? ''}`}
{/if}
diff --git a/frontend/src/lib/components/dbOps.ts b/frontend/src/lib/components/dbOps.ts
index 02c26f034f..e239a9e957 100644
--- a/frontend/src/lib/components/dbOps.ts
+++ b/frontend/src/lib/components/dbOps.ts
@@ -299,7 +299,7 @@ export function dbSchemaOpsWithPreviewScripts({
? input.resourcePath.slice('datatable://'.length)
: undefined
// A migration declaring no role runs as admin, whatever role the manager connects as.
- const migrationRole = input.type === 'database' ? input.role : undefined
+ const migrationRole = input.type === 'database' ? (input.role ?? input.migrationRole) : undefined
function makeMarker(op: string, payload: Record): string {
if (ducklake) payload.ducklake = ducklake
diff --git a/frontend/src/lib/components/dbTypes.ts b/frontend/src/lib/components/dbTypes.ts
index a9cb0a81e1..e6d3af12e8 100644
--- a/frontend/src/lib/components/dbTypes.ts
+++ b/frontend/src/lib/components/dbTypes.ts
@@ -6,6 +6,9 @@ export type DbInput =
/** The data table role to connect as; the data table's default when unset. Only
* meaningful for a `datatable://` path. */
role?: string
+ /** The role migrations written through this input declare when `role` is unset. A
+ * migration declaring none runs as admin, not as the role the manager connects as. */
+ migrationRole?: string
specificSchema?: string
specificTable?: string
}
@@ -42,6 +45,16 @@ export function datatableNameTakesRole(name: string): boolean {
return !name.includes('?')
}
+/** The `migrationRole` of a data table that cannot name a role in its reference: it connects as
+ * its default role, which its migrations must then declare. */
+export function defaultMigrationRole(
+ name: string,
+ permissioned: boolean | undefined,
+ defaultRole: string | undefined
+): string | undefined {
+ return permissioned && !datatableNameTakesRole(name) ? defaultRole : undefined
+}
+
/** `datatable://`, with `?role=` when a role is named. Throws rather than build a
* reference the executor would refuse, or one that would silently mean another role. */
export function datatableReference(name: string, role: string | undefined): string {
diff --git a/frontend/src/lib/components/raw_apps/RawAppDataTableDrawer.svelte b/frontend/src/lib/components/raw_apps/RawAppDataTableDrawer.svelte
index a61bf0a0cb..f0ea7d1ce3 100644
--- a/frontend/src/lib/components/raw_apps/RawAppDataTableDrawer.svelte
+++ b/frontend/src/lib/components/raw_apps/RawAppDataTableDrawer.svelte
@@ -11,7 +11,12 @@
import { resource } from 'runed'
import { ArrowLeft, Expand, Minimize, Plus, RefreshCcw } from 'lucide-svelte'
import DBManagerContent from '../DBManagerContent.svelte'
- import { ADMIN_DATATABLE_ROLE, datatableNameTakesRole, type DbInput } from '../dbTypes'
+ import {
+ ADMIN_DATATABLE_ROLE,
+ datatableNameTakesRole,
+ defaultMigrationRole,
+ type DbInput
+ } from '../dbTypes'
import type { PendingRowAction, SelectedTable } from '../DBManager.svelte'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useDbManagerTag } from '../dbManagerTag.svelte'
@@ -107,12 +112,12 @@
// without one runs, and caches, as whatever the server defaults to.
const roleSettled = $derived(
selectedDatatable === undefined ||
- // Its reference cannot name a role, so it connects as the default one.
- !datatableNameTakesRole(selectedDatatable) ||
(rolesOfCurrent !== undefined &&
(!rolesOfCurrent.permissioned ||
rolesOfCurrent.roles.length === 0 ||
- selectedRole !== undefined))
+ selectedRole !== undefined ||
+ // Its reference cannot name a role, so it connects as the default one.
+ !datatableNameTakesRole(selectedDatatable)))
)
$effect(() => {
@@ -307,6 +312,11 @@
resourceType: 'postgresql' as const,
resourcePath: `datatable://${selectedDatatable}`,
role: selectedRole,
+ migrationRole: defaultMigrationRole(
+ selectedDatatable,
+ rolesOfCurrent?.permissioned,
+ rolesOfCurrent?.default_role
+ ),
specificSchema: openSchemaKey,
specificTable: openTableKey
}
diff --git a/frontend/src/lib/components/raw_apps/RawAppTemplatePicker.svelte b/frontend/src/lib/components/raw_apps/RawAppTemplatePicker.svelte
index 1c889d7eba..a48713a501 100644
--- a/frontend/src/lib/components/raw_apps/RawAppTemplatePicker.svelte
+++ b/frontend/src/lib/components/raw_apps/RawAppTemplatePicker.svelte
@@ -34,7 +34,7 @@
toDatatableItems,
toSchemaItems
} from './datatableUtils.svelte'
- import { datatableNameTakesRole } from '../dbTypes'
+ import { datatableNameTakesRole, defaultMigrationRole } from '../dbTypes'
import RawAppDataTableList from './RawAppDataTableList.svelte'
import RawAppDataTableDrawer from './RawAppDataTableDrawer.svelte'
import FileEditorIcon from './FileEditorIcon.svelte'
@@ -290,7 +290,12 @@
type: 'database',
resourceType: 'postgresql',
resourcePath: `datatable://${selectedDatatable}`,
- role: effectiveRole
+ role: effectiveRole,
+ migrationRole: defaultMigrationRole(
+ selectedDatatable,
+ roles.current.permissioned,
+ roles.current.defaultRole
+ )
}
})
await dbOps.onCreateSchema({ schema: newSchemaName })