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 })