diff --git a/frontend/src/lib/components/DatatableSchemaDiff.svelte b/frontend/src/lib/components/DatatableSchemaDiff.svelte index 8cfe81a774..69e657a958 100644 --- a/frontend/src/lib/components/DatatableSchemaDiff.svelte +++ b/frontend/src/lib/components/DatatableSchemaDiff.svelte @@ -6,14 +6,13 @@ } from '$lib/components/apps/components/display/dbtable/tableEditor' import { diffTableEditorValues, - type AlterTableValues + type AlterTableValues, + makeAlterTableQueries } from '$lib/components/apps/components/display/dbtable/queries/alterTable' import type { GetDatatableFullSchemaResponse } from '$lib/gen' - /** Full database schema: { schema_name: { table_name: TableEditorValues } } */ export type DatabaseSchema = Record> - /** Convert backend schema response to TableEditorValues format */ export function apiSchemaToEditorSchema( apiSchema: GetDatatableFullSchemaResponse ): DatabaseSchema { @@ -62,34 +61,29 @@ datatableName: string aheadChanges: TableDiff[] behindChanges: TableDiff[] + originalSchema: DatabaseSchema + parentSchema: DatabaseSchema + forkSchema: DatabaseSchema } - /** - * Diff two full database schemas, returning per-table diffs. - * Uses diffTableEditorValues for tables that exist in both. - */ export function diffDatabaseSchemas( original: DatabaseSchema, current: DatabaseSchema ): TableDiff[] { const diffs: TableDiff[] = [] - const allSchemas = new Set([...Object.keys(original), ...Object.keys(current)]) for (const schemaName of allSchemas) { const origTables = original[schemaName] ?? {} const currTables = current[schemaName] ?? {} const allTables = new Set([...Object.keys(origTables), ...Object.keys(currTables)]) - for (const tableName of allTables) { const origTable = origTables[tableName] const currTable = currTables[tableName] - if (!origTable && currTable) { diffs.push({ schemaName, tableName, kind: 'added' }) } else if (origTable && !currTable) { diffs.push({ schemaName, tableName, kind: 'removed' }) } else if (origTable && currTable) { - // Set initialName on current columns so diffTableEditorValues can track renames const currWithInitial: TableEditorValues = { ...currTable, columns: currTable.columns.map((col) => ({ @@ -104,15 +98,9 @@ } } } - return diffs } - /** - * For a forked datatable, compute ahead/behind diffs by comparing: - * - (original, parent) → behind changes (parent drifted) - * - (original, fork) → ahead changes (fork drifted) - */ export function computeDatatableDiff( datatableName: string, originalSchema: DatabaseSchema, @@ -122,14 +110,60 @@ return { datatableName, behindChanges: diffDatabaseSchemas(originalSchema, parentSchema), - aheadChanges: diffDatabaseSchemas(originalSchema, forkSchema) + aheadChanges: diffDatabaseSchemas(originalSchema, forkSchema), + originalSchema, + parentSchema, + forkSchema } } + + export function generateMigrationSql(change: TableDiff, sourceSchema: DatabaseSchema): string { + if (change.kind === 'modified' && change.operations) { + const queries = makeAlterTableQueries(change.operations, 'postgresql', change.schemaName) + if (queries.length === 0) return '' + return 'BEGIN;\n' + queries.join('\n') + '\nCOMMIT;' + } + if (change.kind === 'added') { + const table = sourceSchema[change.schemaName]?.[change.tableName] + if (!table) return '' + const colDefs = table.columns + .map((c) => { + let def = `"${c.name}" ${c.datatype}` + if (c.nullable === false) def += ' NOT NULL' + if (c.defaultValue) def += ` DEFAULT ${c.defaultValue}` + return def + }) + .join(',\n ') + const pkCols = table.columns.filter((c) => c.primaryKey).map((c) => `"${c.name}"`) + const pkLine = pkCols.length > 0 ? `,\n PRIMARY KEY (${pkCols.join(', ')})` : '' + return `BEGIN;\nCREATE TABLE "${change.schemaName}"."${change.tableName}" (\n ${colDefs}${pkLine}\n);\nCOMMIT;` + } + if (change.kind === 'removed') { + return `BEGIN;\nDROP TABLE IF EXISTS "${change.schemaName}"."${change.tableName}";\nCOMMIT;` + } + return '' + } {#if loading} @@ -273,19 +401,27 @@
{#if diff.aheadChanges.length > 0}
-
Fork changes (ahead)
+
Fork changes (ahead)
{#each diff.aheadChanges as change}
{#if change.kind === 'added'} - + {:else if change.kind === 'removed'} - + {:else} - + {/if} {change.schemaName}. {change.tableName} - {operationSummary(change)} + {operationSummary(change)} +
{/each}
@@ -298,15 +434,23 @@ {#each diff.behindChanges as change}
{#if change.kind === 'added'} - + {:else if change.kind === 'removed'} - + {:else} - + {/if} {change.schemaName}. {change.tableName} - {operationSummary(change)} + {operationSummary(change)} +
{/each}
@@ -317,3 +461,79 @@ {/each} {/if} + + + + {#if reviewChange && reviewDiff} + {@const yaml = getYamlDiff(reviewChange, reviewDiff)} +
+
+

+ {reviewChange.schemaName}.{reviewChange.tableName} +

+
+ + +
+
+
+
+
+ Parent ({parentWorkspaceId}) +
+
{yaml.parent}
+
+
+
+ Fork ({currentWorkspaceId}) +
+
{yaml.fork}
+
+
+
+ {/if} +
+ + + + {#if reviewChange && reviewDiff && migrationDirection} + {@const targetLabel = migrationDirection === 'to_fork' ? currentWorkspaceId : parentWorkspaceId} +
+
+

+ Migrate {reviewChange.schemaName}.{reviewChange.tableName} → {targetLabel} +

+
+
+ +
+
+ + +
+
+ {/if} +