diff --git a/drizzle-kit/src/jsonStatements.ts b/drizzle-kit/src/jsonStatements.ts index 47cb08908..a3df984a2 100644 --- a/drizzle-kit/src/jsonStatements.ts +++ b/drizzle-kit/src/jsonStatements.ts @@ -106,6 +106,15 @@ export interface JsonAddValueToEnumStatement { before: string; } +export interface JsonDropValueFromEnumStatement { + type: 'alter_type_drop_value'; + name: string; + schema: string; + deletedValues: string[]; + newValues: string[]; + columnsWithEnum: { schema: string; table: string; column: string }[]; +} + export interface JsonCreateSequenceStatement { type: 'create_sequence'; name: string; @@ -582,7 +591,8 @@ export type JsonStatement = | JsonDropSequenceStatement | JsonCreateSequenceStatement | JsonMoveSequenceStatement - | JsonRenameSequenceStatement; + | JsonRenameSequenceStatement + | JsonDropValueFromEnumStatement; export const preparePgCreateTableJson = ( table: Table, @@ -717,6 +727,36 @@ export const prepareAddValuesToEnumJson = ( }); }; +export const prepareDropEnumValues = ( + name: string, + schema: string, + removedValues: string[], + json2: PgSchema, +): JsonDropValueFromEnumStatement[] => { + if (!removedValues.length) return []; + + const affectedColumns: { schema: string; table: string; column: string }[] = []; + + for (const tableKey in json2.tables) { + const table = json2.tables[tableKey]; + for (const columnKey in table.columns) { + const column = table.columns[columnKey]; + if (column.type === name && column.typeSchema === schema) { + affectedColumns.push({ schema: table.schema || 'public', table: table.name, column: column.name }); + } + } + } + + return [{ + type: 'alter_type_drop_value', + name: name, + schema: schema, + deletedValues: removedValues, + newValues: json2.enums[`${schema}.${name}`].values, + columnsWithEnum: affectedColumns, + }]; +}; + export const prepareDropEnumJson = ( name: string, schema: string, diff --git a/drizzle-kit/src/snapshotsDiffer.ts b/drizzle-kit/src/snapshotsDiffer.ts index 64ea8e465..cf7b5f793 100644 --- a/drizzle-kit/src/snapshotsDiffer.ts +++ b/drizzle-kit/src/snapshotsDiffer.ts @@ -56,6 +56,7 @@ import { prepareDeleteSchemasJson as prepareDropSchemasJson, prepareDeleteUniqueConstraintPg as prepareDeleteUniqueConstraint, prepareDropEnumJson, + prepareDropEnumValues, prepareDropIndexesJson, prepareDropReferencesJson, prepareDropSequenceJson, @@ -1002,30 +1003,6 @@ export const applyPgSnapshotsDiff = async ( // - create table with generated // - alter - should be not triggered, but should get warning - // TODO: - // let hasEnumValuesDeletions = false; - // let enumValuesDeletions: { name: string; schema: string; values: string[] }[] = - // []; - // for (let alteredEnum of typedResult.alteredEnums) { - // if (alteredEnum.deletedValues.length > 0) { - // hasEnumValuesDeletions = true; - // enumValuesDeletions.push({ - // name: alteredEnum.name, - // schema: alteredEnum.schema, - // values: alteredEnum.deletedValues, - // }); - // } - // } - // if (hasEnumValuesDeletions) { - // console.log(error("Deletion of enum values is prohibited in Postgres - see here")); - // for(let entry of enumValuesDeletions){ - // console.log(error(`You're trying to delete ${chalk.blue(`[${entry.values.join(", ")}]`)} values from ${chalk.blue(`${entry.schema}.${entry.name}`)}`)) - // } - // } - // if (hasEnumValuesDeletions && action === "push") { - // process.exit(1); - // } - const createEnums = createdEnums.map((it) => { return prepareCreateEnumJson(it.name, it.schema, it.values); }) ?? []; @@ -1042,14 +1019,17 @@ export const applyPgSnapshotsDiff = async ( return prepareRenameEnumJson(it.from.name, it.to.name, it.to.schema); }); - // todo: block enum rename, enum value rename and enun deletion for now const jsonAlterEnumsWithAddedValues = typedResult.alteredEnums .map((it) => { return prepareAddValuesToEnumJson(it.name, it.schema, it.addedValues); }) .flat() ?? []; - /////////// + const jsonAlterEnumsWithDroppedValues = typedResult.alteredEnums + .map((it) => { + return prepareDropEnumValues(it.name, it.schema, it.deletedValues, curFull); + }) + .flat() ?? []; const createSequences = createdSequences.map((it) => { return prepareCreateSequenceJson(it); @@ -1135,6 +1115,7 @@ export const applyPgSnapshotsDiff = async ( jsonStatements.push(...jsonAddedUniqueConstraints); jsonStatements.push(...jsonAlteredUniqueConstraints); + jsonStatements.push(...jsonAlterEnumsWithDroppedValues); jsonStatements.push(...dropEnums); jsonStatements.push(...dropSequences); @@ -1169,7 +1150,25 @@ export const applyPgSnapshotsDiff = async ( return true; }); - const sqlStatements = fromJson(filteredJsonStatements, 'postgresql'); + // enum filters + // Need to find add and drop enum values in same enum and remove add values + const filteredEnumsJsonStatements = filteredJsonStatements.filter((st) => { + if (st.type === 'alter_type_add_value') { + if ( + jsonStatements.find( + (it) => + it.type === 'alter_type_drop_value' + && it.name === st.name + && it.schema === st.schema, + ) + ) { + return false; + } + } + return true; + }); + + const sqlStatements = fromJson(filteredEnumsJsonStatements, 'postgresql'); const uniqueSqlStatements: string[] = []; sqlStatements.forEach((ss) => { @@ -1190,7 +1189,7 @@ export const applyPgSnapshotsDiff = async ( const _meta = prepareMigrationMeta(rSchemas, rTables, rColumns); return { - statements: filteredJsonStatements, + statements: filteredEnumsJsonStatements, sqlStatements: uniqueSqlStatements, _meta, }; diff --git a/drizzle-kit/src/sqlgenerator.ts b/drizzle-kit/src/sqlgenerator.ts index 374b30581..9adfd1b16 100644 --- a/drizzle-kit/src/sqlgenerator.ts +++ b/drizzle-kit/src/sqlgenerator.ts @@ -38,13 +38,17 @@ import { JsonDeleteReferenceStatement, JsonDeleteUniqueConstraint, JsonDropColumnStatement, + JsonDropEnumStatement, JsonDropIndexStatement, JsonDropSequenceStatement, JsonDropTableStatement, + JsonDropValueFromEnumStatement, + JsonMoveEnumStatement, JsonMoveSequenceStatement, JsonPgCreateIndexStatement, JsonRecreateTableStatement, JsonRenameColumnStatement, + JsonRenameEnumStatement, JsonRenameSchema, JsonRenameSequenceStatement, JsonRenameTableStatement, @@ -694,22 +698,39 @@ class CreateTypeEnumConvertor extends Convertor { convert(st: JsonCreateEnumStatement) { const { name, values, schema } = st; - const tableNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; let valuesStatement = '('; valuesStatement += values.map((it) => `'${it}'`).join(', '); valuesStatement += ')'; - let statement = 'DO $$ BEGIN'; - statement += '\n'; - statement += ` CREATE TYPE ${tableNameWithSchema} AS ENUM${valuesStatement};`; - statement += '\n'; - statement += 'EXCEPTION'; - statement += '\n'; - statement += ' WHEN duplicate_object THEN null;'; - statement += '\n'; - statement += 'END $$;'; - statement += '\n'; + // TODO do we need this? + // let statement = 'DO $$ BEGIN'; + // statement += '\n'; + let statement = `CREATE TYPE ${enumNameWithSchema} AS ENUM${valuesStatement};`; + // statement += '\n'; + // statement += 'EXCEPTION'; + // statement += '\n'; + // statement += ' WHEN duplicate_object THEN null;'; + // statement += '\n'; + // statement += 'END $$;'; + // statement += '\n'; + return statement; + } +} + +class DropTypeEnumConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'drop_type_enum'; + } + + convert(st: JsonDropEnumStatement) { + const { name, schema } = st; + + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + + let statement = `DROP TYPE ${enumNameWithSchema};`; + return statement; } } @@ -720,9 +741,74 @@ class AlterTypeAddValueConvertor extends Convertor { } convert(st: JsonAddValueToEnumStatement) { - const { name, schema, value } = st; - const schemaPrefix = schema && schema !== 'public' ? `"${schema}".` : ''; - return `ALTER TYPE ${schemaPrefix}"${name}" ADD VALUE '${value}';`; + const { name, schema, value, before } = st; + + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + + return `ALTER TYPE ${enumNameWithSchema} ADD VALUE '${value}'${before.length ? ` BEFORE '${before}'` : ''};`; + } +} + +class AlterTypeSetSchemaConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'move_type_enum'; + } + + convert(st: JsonMoveEnumStatement) { + const { name, schemaFrom, schemaTo } = st; + + const enumNameWithSchema = schemaFrom ? `"${schemaFrom}"."${name}"` : `"${name}"`; + + return `ALTER TYPE ${enumNameWithSchema} SET SCHEMA "${schemaTo}";`; + } +} + +class AlterRenameTypeConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'rename_type_enum'; + } + + convert(st: JsonRenameEnumStatement) { + const { nameTo, nameFrom, schema } = st; + + const enumNameWithSchema = schema ? `"${schema}"."${nameFrom}"` : `"${nameFrom}"`; + + return `ALTER TYPE ${enumNameWithSchema} RENAME TO "${nameTo}";`; + } +} + +class AlterTypeDropValueConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'alter_type_drop_value'; + } + + convert(st: JsonDropValueFromEnumStatement) { + const { columnsWithEnum, name, newValues, schema } = st; + + const statements: string[] = []; + + for (const withEnum of columnsWithEnum) { + statements.push( + `ALTER TABLE "${withEnum.schema}"."${withEnum.table}" ALTER COLUMN "${withEnum.column}" SET DATA TYPE text;`, + ); + } + + statements.push(new DropTypeEnumConvertor().convert({ name: name, schema, type: 'drop_type_enum' })); + + statements.push(new CreateTypeEnumConvertor().convert({ + name: name, + schema: schema, + values: newValues, + type: 'create_type_enum', + })); + + for (const withEnum of columnsWithEnum) { + statements.push( + `ALTER TABLE "${withEnum.schema}"."${withEnum.table}" ALTER COLUMN "${withEnum.column}" SET DATA TYPE "${schema}"."${name}" USING "${withEnum.column}"::"${schema}"."${name}";`, + ); + } + + return statements; } } @@ -2487,6 +2573,11 @@ convertors.push(new SQLiteRecreateTableConvertor()); convertors.push(new LibSQLRecreateTableConvertor()); convertors.push(new CreateTypeEnumConvertor()); +convertors.push(new DropTypeEnumConvertor()); +convertors.push(new AlterTypeAddValueConvertor()); +convertors.push(new AlterTypeSetSchemaConvertor()); +convertors.push(new AlterRenameTypeConvertor()); +convertors.push(new AlterTypeDropValueConvertor()); convertors.push(new CreatePgSequenceConvertor()); convertors.push(new DropPgSequenceConvertor()); @@ -2530,8 +2621,6 @@ convertors.push(new PgDropIndexConvertor()); convertors.push(new SqliteDropIndexConvertor()); convertors.push(new MySqlDropIndexConvertor()); -convertors.push(new AlterTypeAddValueConvertor()); - convertors.push(new PgAlterTableAlterColumnSetPrimaryKeyConvertor()); convertors.push(new PgAlterTableAlterColumnDropPrimaryKeyConvertor()); convertors.push(new PgAlterTableAlterColumnSetNotNullConvertor()); diff --git a/drizzle-kit/tests/pg-enums.test.ts b/drizzle-kit/tests/pg-enums.test.ts index cd8877a43..2d7f44a22 100644 --- a/drizzle-kit/tests/pg-enums.test.ts +++ b/drizzle-kit/tests/pg-enums.test.ts @@ -7,8 +7,10 @@ test('enums #1', async () => { enum: pgEnum('enum', ['value']), }; - const { statements } = await diffTestSchemas({}, to, []); + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value');`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ name: 'enum', @@ -24,8 +26,10 @@ test('enums #2', async () => { enum: folder.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas({}, to, []); + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TYPE "folder"."enum" AS ENUM('value');`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ name: 'enum', @@ -40,8 +44,10 @@ test('enums #3', async () => { enum: pgEnum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, {}, []); + const { statements, sqlStatements } = await diffTestSchemas(from, {}, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP TYPE "public"."enum";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'drop_type_enum', @@ -57,8 +63,10 @@ test('enums #4', async () => { enum: folder.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, {}, []); + const { statements, sqlStatements } = await diffTestSchemas(from, {}, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP TYPE "folder"."enum";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'drop_type_enum', @@ -81,8 +89,10 @@ test('enums #5', async () => { enum: folder2.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, to, ['folder1->folder2']); + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['folder1->folder2']); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER SCHEMA "folder1" RENAME TO "folder2";\n`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_schema', @@ -107,10 +117,12 @@ test('enums #6', async () => { enum: folder2.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum->folder2.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum" SET SCHEMA "folder2";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -129,8 +141,10 @@ test('enums #7', async () => { enum: pgEnum('enum', ['value1', 'value2']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -150,8 +164,11 @@ test('enums #8', async () => { enum: pgEnum('enum', ['value1', 'value2', 'value3']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2';`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value3';`); expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -179,8 +196,10 @@ test('enums #9', async () => { enum: pgEnum('enum', ['value1', 'value2', 'value3']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2' BEFORE 'value3';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -201,8 +220,10 @@ test('enums #10', async () => { enum: schema.enum('enum', ['value1', 'value2']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder"."enum" ADD VALUE 'value2';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -223,10 +244,12 @@ test('enums #11', async () => { enum: pgEnum('enum', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum->public.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum" SET SCHEMA "public";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -246,10 +269,12 @@ test('enums #12', async () => { enum: schema1.enum('enum', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum->folder1.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" SET SCHEMA "folder1";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -268,10 +293,12 @@ test('enums #13', async () => { enum: pgEnum('enum2', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->public.enum2', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" RENAME TO "enum2";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_type_enum', @@ -292,10 +319,13 @@ test('enums #14', async () => { enum: folder2.enum('enum2', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum1->folder2.enum2', ]); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum1" SET SCHEMA "folder2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "folder2"."enum1" RENAME TO "enum2";`); expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -322,10 +352,16 @@ test('enums #15', async () => { enum: folder2.enum('enum2', ['value1', 'value2', 'value3', 'value4']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum1->folder2.enum2', ]); + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum1" SET SCHEMA "folder2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "folder2"."enum1" RENAME TO "enum2";`); + expect(sqlStatements[2]).toBe(`ALTER TYPE "folder2"."enum2" ADD VALUE 'value2' BEFORE 'value4';`); + expect(sqlStatements[3]).toBe(`ALTER TYPE "folder2"."enum2" ADD VALUE 'value3' BEFORE 'value4';`); + expect(statements.length).toBe(4); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -373,10 +409,13 @@ test('enums #16', async () => { }), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->public.enum2', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" RENAME TO "enum2";`); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_type_enum', @@ -405,10 +444,14 @@ test('enums #17', async () => { }), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->schema.enum1', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" SET SCHEMA "schema";`); + + expect(sqlStatements.length).toBe(1); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -440,10 +483,14 @@ test('enums #18', async () => { }; // change name and schema of the enum, no table changes - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'schema1.enum1->schema2.enum2', ]); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "schema1"."enum1" SET SCHEMA "schema2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "schema2"."enum1" RENAME TO "enum2";`); + expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -458,3 +505,177 @@ test('enums #18', async () => { schema: 'schema2', }); }); + +test('drop enum value', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const from = { + enum1, + }; + + const enum2 = pgEnum('enum', ['value1', 'value3']); + const to = { + enum2, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[1]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3');`); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [], + deletedValues: [ + 'value2', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); + +test('drop enum value. enum is columns data type', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const schema = pgSchema('new_schema'); + + const from = { + schema, + enum1, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const enum2 = pgEnum('enum', ['value1', 'value3']); + const to = { + schema, + enum2, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[2]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[3]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3');`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [ + { + column: 'column', + schema: 'public', + table: 'table', + }, + { + column: 'column', + schema: 'new_schema', + table: 'table', + }, + ], + deletedValues: [ + 'value2', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); + +test('shuffle enum values', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const schema = pgSchema('new_schema'); + + const from = { + schema, + enum1, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const enum2 = pgEnum('enum', ['value1', 'value3', 'value2']); + const to = { + schema, + enum2, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[2]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[3]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3', 'value2');`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + + console.log('statements: ', statements); + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [ + { + column: 'column', + schema: 'public', + table: 'table', + }, + { + column: 'column', + schema: 'new_schema', + table: 'table', + }, + ], + deletedValues: [ + 'value3', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + 'value2', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); diff --git a/drizzle-kit/tests/push/pg.test.ts b/drizzle-kit/tests/push/pg.test.ts index cb1a97122..243e22242 100644 --- a/drizzle-kit/tests/push/pg.test.ts +++ b/drizzle-kit/tests/push/pg.test.ts @@ -30,7 +30,7 @@ import { import { drizzle } from 'drizzle-orm/pglite'; import { SQL, sql } from 'drizzle-orm/sql'; import { pgSuggestions } from 'src/cli/commands/pgPushUtils'; -import { diffTestSchemasPush } from 'tests/schemaDiffer'; +import { diffTestSchemas, diffTestSchemasPush } from 'tests/schemaDiffer'; import { afterEach, expect, test } from 'vitest'; import { DialectSuite, run } from './common'; @@ -2236,3 +2236,189 @@ test('add array column - default', async () => { 'ALTER TABLE "test" ADD COLUMN "values" integer[] DEFAULT \'{1,2,3}\';', ]); }); + +test('enums ordering', async () => { + const enum1 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema1 = {}; + + const schema2 = { + enum1, + }; + + const { sqlStatements: createEnum } = await diffTestSchemas(schema1, schema2, []); + + const enum2 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema3 = { + enum2, + }; + + const { sqlStatements: addedValueSql } = await diffTestSchemas(schema2, schema3, []); + + const enum3 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'addedToMiddle', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema4 = { + enum3, + }; + + const client = new PGlite(); + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema3, + schema4, + [], + false, + ['public'], + undefined, + [...createEnum, ...addedValueSql], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + before: 'custMgf', + name: 'enum_users_customer_and_ship_to_settings_roles', + schema: 'public', + type: 'alter_type_add_value', + value: 'addedToMiddle', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TYPE "public"."enum_users_customer_and_ship_to_settings_roles" ADD VALUE 'addedToMiddle' BEFORE 'custMgf';`, + ); +}); + +test('drop enum values', async () => { + const newSchema = pgSchema('mySchema'); + const enum3 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'addedToMiddle', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema1 = { + enum3, + table: pgTable('enum_table', { + id: enum3(), + }), + newSchema, + table1: newSchema.table('enum_table', { + id: enum3(), + }), + }; + + const enum4 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema2 = { + enum4, + table: pgTable('enum_table', { + id: enum4(), + }), + newSchema, + table1: newSchema.table('enum_table', { + id: enum4(), + }), + }; + + const client = new PGlite(); + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public', 'mySchema'], + undefined, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'enum_users_customer_and_ship_to_settings_roles', + schema: 'public', + type: 'alter_type_drop_value', + newValues: [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ], + deletedValues: ['addedToMiddle', 'custMgf'], + columnsWithEnum: [{ + column: 'id', + schema: 'public', + table: 'enum_table', + }, { + column: 'id', + schema: 'mySchema', + table: 'enum_table', + }], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "public"."enum_table" ALTER COLUMN "id" SET DATA TYPE text;`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE "mySchema"."enum_table" ALTER COLUMN "id" SET DATA TYPE text;`, + ); + expect(sqlStatements[2]).toBe( + `DROP TYPE "public"."enum_users_customer_and_ship_to_settings_roles";`, + ); + expect(sqlStatements[3]).toBe( + `CREATE TYPE "public"."enum_users_customer_and_ship_to_settings_roles" AS ENUM('addedToTop', 'custAll', 'custAdmin', 'custClerk', 'custInvoiceManager', 'custApprover', 'custOrderWriter', 'custBuyer');`, + ); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."enum_table" ALTER COLUMN "id" SET DATA TYPE "public"."enum_users_customer_and_ship_to_settings_roles" USING "id"::"public"."enum_users_customer_and_ship_to_settings_roles";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "mySchema"."enum_table" ALTER COLUMN "id" SET DATA TYPE "public"."enum_users_customer_and_ship_to_settings_roles" USING "id"::"public"."enum_users_customer_and_ship_to_settings_roles";`, + ); +}); diff --git a/drizzle-kit/tests/schemaDiffer.ts b/drizzle-kit/tests/schemaDiffer.ts index 22a79ef72..5dca0c122 100644 --- a/drizzle-kit/tests/schemaDiffer.ts +++ b/drizzle-kit/tests/schemaDiffer.ts @@ -414,9 +414,14 @@ export const diffTestSchemasPush = async ( cli: boolean = false, schemas: string[] = ['public'], casing?: CasingType | undefined, + sqlStatementsToRun: string[] = [], ) => { - const { sqlStatements } = await applyPgDiffs(left, casing); - for (const st of sqlStatements) { + if (!sqlStatementsToRun.length) { + const res = await applyPgDiffs(left, casing); + sqlStatementsToRun = res.sqlStatements; + } + + for (const st of sqlStatementsToRun) { await client.query(st); } @@ -992,13 +997,6 @@ export async function diffTestSchemasPushLibSQL( run: async (query: string) => { await client.execute(query); }, - batch: async ( - queries: { query: string; values?: any[] | undefined }[], - ) => { - await client.batch( - queries.map((it) => ({ sql: it.query, args: it.values ?? [] })), - ); - }, }, undefined, ); @@ -1058,13 +1056,6 @@ export async function diffTestSchemasPushLibSQL( run: async (query: string) => { await client.execute(query); }, - batch: async ( - queries: { query: string; values?: any[] | undefined }[], - ) => { - await client.batch( - queries.map((it) => ({ sql: it.query, args: it.values ?? [] })), - ); - }, }, statements, sn1,