fix(sqlserver): recreate primary key when cloning tables
This commit is contained in:
1 parent
832e08ae1f
commit
7afa87e849
5 files changed
+189
-3
No files matched your search
@@ -425,6 +425,44 @@ describe("buildDuplicateTableStructurePlan", () => {
|
||||
expect(plan).toEqual({ sql: 'CREATE TABLE "copy" (LIKE "source" INCLUDING ALL);', sourceColumns: undefined, executeAsScript: false });
|
||||
});
|
||||
|
||||
it("recreates the SQL Server primary key that SELECT INTO drops", async () => {
|
||||
apiMock.listIndexes.mockResolvedValue([
|
||||
{ name: "PK_ORDERS", columns: ["ID", "SEQ"], is_unique: true, is_primary: true },
|
||||
{ name: "IDX_ORDERS_CUSTOMER", columns: ["CUSTOMER"], is_unique: false, is_primary: false },
|
||||
]);
|
||||
apiMock.buildDuplicateTableStructureSql.mockResolvedValue("SELECT TOP 0 * INTO [dbo].[orders_copy] FROM [dbo].[orders];\nALTER TABLE [dbo].[orders_copy] ADD CONSTRAINT [PK_orders_copy] PRIMARY KEY ([ID], [SEQ]);");
|
||||
|
||||
const plan = await buildDuplicateTableStructurePlan({
|
||||
connectionId: "mssql-1",
|
||||
database: "app",
|
||||
databaseType: "sqlserver",
|
||||
schema: "dbo",
|
||||
sourceName: "orders",
|
||||
targetName: "orders_copy",
|
||||
});
|
||||
|
||||
expect(apiMock.listIndexes).toHaveBeenCalledWith("mssql-1", "app", "dbo", "orders", undefined);
|
||||
expect(apiMock.buildDuplicateTableStructureSql).toHaveBeenCalledWith(expect.objectContaining({ databaseType: "sqlserver", primaryKeyColumns: ["ID", "SEQ"] }));
|
||||
expect(plan.executeAsScript).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps primary-key-free SQL Server clones on a single statement", async () => {
|
||||
apiMock.listIndexes.mockResolvedValue([{ name: "IDX_ORDERS_CUSTOMER", columns: ["CUSTOMER"], is_unique: false, is_primary: false }]);
|
||||
apiMock.buildDuplicateTableStructureSql.mockResolvedValue("SELECT TOP 0 * INTO [orders_copy] FROM [orders];");
|
||||
|
||||
const plan = await buildDuplicateTableStructurePlan({
|
||||
connectionId: "mssql-1",
|
||||
database: "app",
|
||||
databaseType: "sqlserver",
|
||||
schema: undefined,
|
||||
sourceName: "orders",
|
||||
targetName: "orders_copy",
|
||||
});
|
||||
|
||||
expect(apiMock.buildDuplicateTableStructureSql).toHaveBeenCalledWith(expect.objectContaining({ databaseType: "sqlserver", primaryKeyColumns: [] }));
|
||||
expect(plan).toEqual({ sql: "SELECT TOP 0 * INTO [orders_copy] FROM [orders];", sourceColumns: undefined, executeAsScript: false });
|
||||
});
|
||||
|
||||
it("forwards the connection identifier quote so dual-dialect clones stay executable", async () => {
|
||||
// Cloud Spanner's PostgreSQL dialect quotes with double quotes while GoogleSQL uses backticks,
|
||||
// and only the connected agent knows which. Dropping the quote here would make the backend fall
|
||||
|
||||
@@ -83,6 +83,9 @@ export interface DuplicateTableStructureSqlOptions {
|
||||
targetName: string;
|
||||
tableComment?: string | null;
|
||||
columnComments?: Array<{ name: string; comment: string }>;
|
||||
/** SQL Server only: source primary-key columns recreated on the clone, because
|
||||
* `SELECT ... INTO` copies columns (and IDENTITY) but drops constraints. */
|
||||
primaryKeyColumns?: string[];
|
||||
/** Quote character reported by the connected server, for types whose quote is not fixed by the
|
||||
* database type alone (Cloud Spanner's two dialects differ). Mirrors `identifierQuote` on the
|
||||
* table-data SQL options. */
|
||||
@@ -264,6 +267,25 @@ export async function buildDuplicateTableStructurePlan(options: DuplicateTableSt
|
||||
return { sql: result.statements.join("\n"), sourceColumns: columns, executeAsScript: true };
|
||||
}
|
||||
|
||||
// `SELECT TOP 0 * INTO` copies columns and the IDENTITY property but drops constraints, so the
|
||||
// cloned table silently loses its primary key (t8y2/dbx#8931). Load the source primary key and
|
||||
// let the backend append an `ALTER TABLE ... ADD CONSTRAINT ... PRIMARY KEY` for it.
|
||||
if (options.databaseType === "sqlserver") {
|
||||
const indexes = await api.listIndexes(options.connectionId, options.database, options.schema || "", options.sourceName, options.catalog);
|
||||
const primaryKeyColumns = indexes.find((index) => index.is_primary && index.columns.length > 0)?.columns ?? [];
|
||||
const sql = await buildDuplicateTableStructureSql({
|
||||
databaseType: options.databaseType,
|
||||
schema: options.schema,
|
||||
sourceName: options.sourceName,
|
||||
targetName: options.targetName,
|
||||
tableComment: options.tableComment,
|
||||
columnComments: [],
|
||||
primaryKeyColumns,
|
||||
identifierQuote: options.identifierQuote,
|
||||
});
|
||||
return { sql, sourceColumns: options.sourceColumns, executeAsScript: primaryKeyColumns.length > 0 || duplicateTableStructureRequiresScript(sql) };
|
||||
}
|
||||
|
||||
const sql = await buildDuplicateTableStructureSql({
|
||||
databaseType: options.databaseType,
|
||||
schema: options.schema,
|
||||
|
||||
Reference in new issue
Block a user