Add check to only create native enum once (#3658)

Co-authored-by: Morgan Zolob <morgan.zolob@ingrooves.com>
This commit is contained in:
Morgan Zolob 2021-01-16 06:16:00 -08:00 committed by GitHub
parent c074ec7b9d
commit 92907e80e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 5 deletions

View File

@ -60,6 +60,9 @@ class TableCompiler_PG extends TableCompiler {
col.columnBuilder.queryContext()
);
// To alter enum columns they must be cast to text first
const isEnum = col.type === 'enu';
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} drop default`,
bindings: [],
@ -69,7 +72,9 @@ class TableCompiler_PG extends TableCompiler {
bindings: [],
});
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} type ${type} using (${colName}::${type})`,
sql: `alter table ${quotedTableName} alter column ${colName} type ${type} using (${colName}${
isEnum ? '::text::' : '::'
}${type})`,
bindings: [],
});

View File

@ -64,10 +64,14 @@ class ColumnCompiler {
}
getColumnType() {
const type = this[this.type];
return typeof type === 'function'
? type.apply(this, tail(this.args))
: type;
// Column type is cached so side effects (such as in pg native enums) are only run once
if (!this._columnType) {
const type = this[this.type];
this._columnType =
typeof type === 'function' ? type.apply(this, tail(this.args)) : type;
}
return this._columnType;
}
getModifiers() {

View File

@ -0,0 +1,107 @@
const { expect } = require('chai');
const {
Db,
getAllDbs,
getKnexForDb,
} = require('../util/knex-instance-provider');
describe('Schema', () => {
describe('native enum columns', () => {
getAllDbs()
// enum useNative is only supported by Postgres
.filter((db) => db === Db.PostgresSQL)
.forEach((db) => {
describe(db, () => {
let knex;
const tblName = 'table_with_native_enum';
const enumColumns = [
{
column: 'enum_col1',
values: ['foo', 'bar'],
typeName: 'test_enum1',
},
{
column: 'enum_col2',
values: ['baz', 'qux'],
typeName: 'test_enum2',
},
];
before(async () => {
knex = getKnexForDb(db);
await knex.schema.dropTableIfExists(tblName);
for (const enumColumn of enumColumns) {
await knex.raw(`DROP TYPE IF EXISTS ${enumColumn.typeName}`);
}
await knex.schema.createTable(tblName, (table) => {
for (const enumColumn of enumColumns) {
table.enum(enumColumn.column, enumColumn.values, {
useNative: true,
enumName: enumColumn.typeName,
});
}
});
});
after(async () => {
await knex.schema.dropTable(tblName);
for (const enumColumn of enumColumns) {
await knex.raw(`DROP TYPE ${enumColumn.typeName}`);
}
return knex.destroy();
});
it('Creates native enums', async () => {
for (const enumColumn of enumColumns) {
const res = await knex
.select('data_type', 'udt_name')
.from('information_schema.columns')
.where({ table_name: tblName, column_name: enumColumn.column });
expect(res[0].data_type).to.equal('USER-DEFINED');
expect(res[0].udt_name).to.equal(enumColumn.typeName);
}
});
describe('Altering', async () => {
const enumColumn = enumColumns[0].column;
const enumTypeName = 'alter_test_enum';
before(async () => {
await knex.raw(`DROP TYPE IF EXISTS ${enumTypeName}`);
});
after(async () => {
await knex.schema.alterTable(tblName, (table) => {
table.dropColumn(enumColumn);
});
await knex.raw(`DROP TYPE ${enumTypeName}`);
});
it('Allows altering native enums', async () => {
await knex.schema.alterTable(tblName, (table) => {
table
.enum(enumColumn, ['altered', 'values'], {
useNative: true,
enumName: enumTypeName,
})
.alter();
});
const res = await knex
.select('data_type', 'udt_name')
.from('information_schema.columns')
.where({ table_name: tblName, column_name: enumColumn });
expect(res[0].data_type).to.equal('USER-DEFINED');
expect(res[0].udt_name).to.equal(enumTypeName);
});
});
});
});
});
});

View File

@ -998,6 +998,85 @@ describe('PostgreSQL SchemaBuilder', function () {
);
});
it('adding enum with useNative and alter', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table
.enu('foo', ['bar', 'baz'], {
useNative: true,
enumName: 'foo_type',
})
.notNullable()
.alter();
})
.toSQL();
equal(5, tableSql.length);
const expectedSql = [
"create type \"foo_type\" as enum ('bar', 'baz')",
'alter table "users" alter column "foo" drop default',
'alter table "users" alter column "foo" drop not null',
'alter table "users" alter column "foo" type "foo_type" using ("foo"::text::"foo_type")',
'alter table "users" alter column "foo" set not null',
];
for (let i = 0; i < tableSql.length; i++) {
expect(tableSql[i].sql).to.equal(expectedSql[i]);
}
});
it('adding multiple useNative enums with some alters', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table
.enu('foo', ['bar', 'baz'], {
useNative: true,
enumName: 'foo_type',
})
.notNullable()
.alter();
table.enu('bar', ['foo', 'baz'], {
useNative: true,
enumName: 'bar_type',
});
table
.enu('baz', ['foo', 'bar'], {
useNative: true,
enumName: 'baz_type',
})
.defaultTo('foo')
.alter();
})
.toSQL();
equal(12, tableSql.length);
const expectedSql = [
"create type \"baz_type\" as enum ('foo', 'bar')",
"create type \"foo_type\" as enum ('bar', 'baz')",
"create type \"bar_type\" as enum ('foo', 'baz')",
'alter table "users" add column "bar" "bar_type"',
'alter table "users" alter column "foo" drop default',
'alter table "users" alter column "foo" drop not null',
'alter table "users" alter column "foo" type "foo_type" using ("foo"::text::"foo_type")',
'alter table "users" alter column "foo" set not null',
'alter table "users" alter column "baz" drop default',
'alter table "users" alter column "baz" drop not null',
'alter table "users" alter column "baz" type "baz_type" using ("baz"::text::"baz_type")',
'alter table "users" alter column "baz" set default \'foo\'',
];
for (let i = 0; i < tableSql.length; i++) {
expect(tableSql[i].sql).to.equal(expectedSql[i]);
}
});
it('adding date', function () {
tableSql = client
.schemaBuilder()