Skip to content

Commit b26d2fe

Browse files
authored
feat: DB Manager alter table (#7486)
1 parent 9f19d91 commit b26d2fe

File tree

16 files changed

+2755
-323
lines changed

16 files changed

+2755
-323
lines changed

frontend/src/lib/components/DBManager.svelte

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
<script lang="ts">
22
import { type DBSchema } from '$lib/stores'
3-
import { ChevronDownIcon, MoreVertical, Plus, Table2, Trash2Icon } from 'lucide-svelte'
3+
import {
4+
ChevronDownIcon,
5+
EditIcon,
6+
Loader2,
7+
MoreVertical,
8+
Plus,
9+
Table2,
10+
Trash2Icon
11+
} from 'lucide-svelte'
412
import { Pane, Splitpanes } from 'svelte-splitpanes'
513
import { ClearableInput, Drawer, DrawerContent } from './common'
614
import { sendUserToast } from '$lib/toast'
7-
import { type ColumnDef } from './apps/components/display/dbtable/utils'
15+
import { type ColumnDef, type DbFeatures } from './apps/components/display/dbtable/utils'
816
import DBTable from './DBTable.svelte'
917
import type { IDbSchemaOps, IDbTableOps } from './dbOps'
1018
import DropdownV2 from './DropdownV2.svelte'
@@ -16,6 +24,12 @@
1624
import Select from './select/Select.svelte'
1725
import { safeSelectItems } from './select/utils.svelte'
1826
import type { Snippet } from 'svelte'
27+
import {
28+
dbSupportsTransactionalDdl,
29+
diffTableEditorValues
30+
} from './apps/components/display/dbtable/queries/alterTable'
31+
import { resource } from 'runed'
32+
import { capitalize, pluralize } from '$lib/utils'
1933
2034
/** Represents a selected table with its schema */
2135
export interface SelectedTable {
@@ -42,6 +56,7 @@
4256
selectedTables?: SelectedTable[]
4357
/** Tables that are already added and should show as disabled */
4458
disabledTables?: SelectedTable[]
59+
features?: DbFeatures
4560
}
4661
let {
4762
dbType,
@@ -58,7 +73,8 @@
5873
dbSelector,
5974
multiSelectMode = false,
6075
selectedTables = $bindable([]),
61-
disabledTables = []
76+
disabledTables = [],
77+
features
6278
}: Props = $props()
6379
6480
// Helper to check if a table is selected in multi-select mode
@@ -187,7 +203,24 @@
187203
| (ConfirmationModal['$$prop_def'] & { onConfirm: () => void })
188204
| undefined = $state()
189205
190-
let dbTableEditorState: { open: boolean } = $state({ open: false })
206+
let dbTableEditorState:
207+
| { open: boolean; alterTableKey?: undefined }
208+
| { open: true; alterTableKey: string } = $state({
209+
open: false
210+
})
211+
let dbTableEditorAlterTableData = resource(
212+
() => dbTableEditorState.alterTableKey,
213+
async (table) => {
214+
if (!table) return
215+
let tableKey2 =
216+
dbSupportsSchemas && selected.schemaKey ? `${selected.schemaKey}.${table}` : table
217+
return await dbSchemaOps.onFetchTableEditorDefinition({
218+
table: table,
219+
getColDefs: () => getColDefs(tableKey2)
220+
})
221+
}
222+
)
223+
191224
let newSchemaDialogOpen = $state(false)
192225
let newSchemaName = $state('')
193226
@@ -297,7 +330,9 @@
297330
<span class="truncate text-ellipsis grow text-left text-tertiary text-xs"
298331
>{schemaKey}</span
299332
>
300-
<span class="text-2xs text-tertiary mr-2 group-hover:hidden">{schemaTables.length}</span>
333+
<span class="text-2xs text-tertiary mr-2 group-hover:hidden">
334+
{schemaTables.length}
335+
</span>
301336
<!-- Delete schema button (on hover) -->
302337
<button
303338
class="hidden group-hover:flex p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors mr-1"
@@ -331,7 +366,8 @@
331366
{#each schemaTables as tableKey}
332367
{@const isDisabled = isTableDisabled(schemaKey, tableKey)}
333368
{@const isChecked = isTableSelected(schemaKey, tableKey) || isDisabled}
334-
{@const isCurrentPreview = selected.schemaKey === schemaKey && selected.tableKey === tableKey}
369+
{@const isCurrentPreview =
370+
selected.schemaKey === schemaKey && selected.tableKey === tableKey}
335371
<div
336372
class={'group w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pl-7 pr-1 ' +
337373
(isCurrentPreview ? 'bg-gray-500/25' : 'hover:bg-gray-500/10') +
@@ -439,6 +475,16 @@
439475
askingForConfirmation = undefined
440476
}
441477
})
478+
},
479+
{
480+
displayName: 'Alter table',
481+
icon: EditIcon,
482+
action: () => {
483+
dbTableEditorState = {
484+
open: true,
485+
alterTableKey: tableKey
486+
}
487+
}
442488
}
443489
]}
444490
class="w-fit"
@@ -490,18 +536,78 @@
490536
open={dbTableEditorState.open}
491537
on:close={() => (dbTableEditorState = { open: false })}
492538
>
493-
<DrawerContent on:close={() => (dbTableEditorState = { open: false })} title="Create a new table">
494-
<DbTableEditor
495-
{dbSchema}
496-
currentSchema={selected.schemaKey}
497-
onConfirm={async (values) => {
498-
await dbSchemaOps.onCreate({ values, schema: selected.schemaKey })
499-
refresh?.()
500-
dbTableEditorState = { open: false }
501-
}}
502-
{dbType}
503-
previewSql={(values) => dbSchemaOps.previewCreateSql({ values, schema: selected.schemaKey })}
504-
/>
539+
<DrawerContent
540+
on:close={() => (dbTableEditorState = { open: false })}
541+
title={dbTableEditorState.alterTableKey
542+
? `Alter ${dbTableEditorState.alterTableKey}`
543+
: 'Create a new table'}
544+
>
545+
{#key dbTableEditorState.alterTableKey}
546+
{#if !dbTableEditorState.alterTableKey || dbTableEditorAlterTableData.current}
547+
<DbTableEditor
548+
{features}
549+
{dbSchema}
550+
currentSchema={selected.schemaKey}
551+
initialValues={dbTableEditorAlterTableData.current}
552+
onConfirm={async ({ values }) => {
553+
if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) {
554+
let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values)
555+
await dbSchemaOps.onAlter({ schema: selected.schemaKey, values: diff })
556+
} else {
557+
await dbSchemaOps.onCreate({ values, schema: selected.schemaKey })
558+
}
559+
refresh?.()
560+
sendUserToast(
561+
dbTableEditorState.alterTableKey
562+
? dbTableEditorState.alterTableKey + ' updated!'
563+
: values.name + ' created!'
564+
)
565+
dbTableEditorState = { open: false }
566+
}}
567+
{dbType}
568+
computePreview={({ values }) => {
569+
if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) {
570+
let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values)
571+
let queries = dbSchemaOps.previewAlterSql({
572+
values: diff,
573+
schema: selected.schemaKey
574+
})
575+
let alert = !dbSupportsTransactionalDdl(dbType)
576+
? {
577+
title: capitalize(dbType) + ' does not support transactional DDL',
578+
body: 'Any of these statements failing may leave your database in an intermediate state.'
579+
}
580+
: undefined
581+
return { sql: queries.join('\n'), ...(alert ? { alert } : {}) }
582+
} else {
583+
return { sql: dbSchemaOps.previewCreateSql({ values, schema: selected.schemaKey }) }
584+
}
585+
}}
586+
computeBtnProps={({ values }) => {
587+
if (dbTableEditorState.alterTableKey && dbTableEditorAlterTableData.current) {
588+
let diff = diffTableEditorValues(dbTableEditorAlterTableData.current, values)
589+
let queries = dbSchemaOps.previewAlterSql({
590+
values: diff,
591+
schema: selected.schemaKey
592+
})
593+
if (!queries.length) {
594+
return { text: 'No changes detected', disabled: true }
595+
}
596+
return {
597+
text: `Alter table (${pluralize(queries.length, 'change')} detected)`
598+
}
599+
} else {
600+
return { text: 'Create table' }
601+
}
602+
}}
603+
/>
604+
{:else if dbTableEditorAlterTableData.loading}
605+
<Loader2 class="animate-spin" size={32} />
606+
{:else}
607+
<p class="text-sm text-tertiary">Failed to load table definition.</p>
608+
<p>{dbTableEditorAlterTableData.error}</p>
609+
{/if}
610+
{/key}
505611
</DrawerContent>
506612
</Drawer>
507613

frontend/src/lib/components/DBManagerContent.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
import { dbSchemas, workspaceStore, type DBSchema } from '$lib/stores'
33
import { sendUserToast, sortArray } from '$lib/utils'
44
import { Loader2 } from 'lucide-svelte'
5-
import { dbSupportsSchemas, type TableMetadata } from './apps/components/display/dbtable/utils'
5+
import {
6+
dbSupportsSchemas,
7+
getDbFeatures,
8+
type TableMetadata
9+
} from './apps/components/display/dbtable/utils'
610
import DbManager from './DBManager.svelte'
711
import {
812
dbSchemaOpsWithPreviewScripts,
@@ -191,7 +195,7 @@
191195
{/if}
192196
</div>
193197
<DbManager
194-
dbSupportsSchemas={input.type == 'database' && dbSupportsSchemas(input.resourceType)}
198+
dbSupportsSchemas={input?.type == 'database' && dbSupportsSchemas(input.resourceType)}
195199
{dbSchema}
196200
{getColDefs}
197201
dbTableOpsFactory={({ colDefs, tableKey }) =>
@@ -215,6 +219,7 @@
215219
{multiSelectMode}
216220
bind:selectedTables
217221
{disabledTables}
222+
features={getDbFeatures(input)}
218223
/>
219224
</Pane>
220225
{#if showRepl}

0 commit comments

Comments
 (0)