不要怂,就是干,撸起袖子干!

Commit fe06b45c by Sushant Committed by GitHub

feat(datatypes): array(enum) support for PostgreSQL (#8738)

1 parent f5fcf164
...@@ -36,6 +36,7 @@ before_script: ...@@ -36,6 +36,7 @@ before_script:
- "if [ $POSTGRES_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-postgres-ramdisk; fi" - "if [ $POSTGRES_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-postgres-ramdisk; fi"
- "if [ $MYSQL_VER ]; then sudo mkdir /mnt/sequelize-mysql-ramdisk; fi" - "if [ $MYSQL_VER ]; then sudo mkdir /mnt/sequelize-mysql-ramdisk; fi"
- "if [ $MYSQL_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-mysql-ramdisk; fi" - "if [ $MYSQL_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-mysql-ramdisk; fi"
# setup docker # setup docker
- "if [ $POSTGRES_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MYSQL_VER}; fi" - "if [ $POSTGRES_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MYSQL_VER}; fi"
- "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi"
......
...@@ -15,7 +15,7 @@ services: ...@@ -15,7 +15,7 @@ services:
# PostgreSQL # PostgreSQL
postgres-95: postgres-95:
image: camptocamp/postgis:9.5 image: sushantdhiman/postgres:9.5
environment: environment:
POSTGRES_USER: sequelize_test POSTGRES_USER: sequelize_test
POSTGRES_PASSWORD: sequelize_test POSTGRES_PASSWORD: sequelize_test
......
...@@ -38,7 +38,7 @@ const Foo = sequelize.define('foo', { ...@@ -38,7 +38,7 @@ const Foo = sequelize.define('foo', {
// The unique property is simply a shorthand to create a unique constraint. // The unique property is simply a shorthand to create a unique constraint.
someUnique: { type: Sequelize.STRING, unique: true }, someUnique: { type: Sequelize.STRING, unique: true },
// It's exactly the same as creating the index in the model's options. // It's exactly the same as creating the index in the model's options.
{ someUnique: { type: Sequelize.STRING } }, { someUnique: { type: Sequelize.STRING } },
{ indexes: [ { unique: true, fields: [ 'someUnique' ] } ] }, { indexes: [ { unique: true, fields: [ 'someUnique' ] } ] },
...@@ -74,7 +74,7 @@ The comment option can also be used on a table, see [model configuration][0] ...@@ -74,7 +74,7 @@ The comment option can also be used on a table, see [model configuration][0]
## Timestamps ## Timestamps
By default, Sequelize will add the attributes `createdAt` and `updatedAt` to your model so you will be able to know when the database entry went into the db and when it was updated last. By default, Sequelize will add the attributes `createdAt` and `updatedAt` to your model so you will be able to know when the database entry went into the db and when it was updated last.
Note that if you are using Sequelize migrations you will need to add the `createdAt` and `updatedAt` fields to your migration definition: Note that if you are using Sequelize migrations you will need to add the `createdAt` and `updatedAt` fields to your migration definition:
...@@ -140,6 +140,7 @@ Sequelize.BOOLEAN // TINYINT(1) ...@@ -140,6 +140,7 @@ Sequelize.BOOLEAN // TINYINT(1)
Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2' Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only. Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM) // Defines an array of ENUM. PostgreSQL only.
Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only. Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB // JSONB column. PostgreSQL only. Sequelize.JSONB // JSONB column. PostgreSQL only.
...@@ -198,6 +199,14 @@ sequelize.define('model', { ...@@ -198,6 +199,14 @@ sequelize.define('model', {
}) })
``` ```
### Array(ENUM)
Its only supported with PostgreSQL.
Array(Enum) type require special treatment. Whenever Sequelize will talk to database it has to typecast Array values with ENUM name.
So this enum name must follow this pattern `enum_<table_name>_<col_name>`. If you are using `sync` then correct name will automatically be generated.
### Range types ### Range types
Since range types have extra information for their bound inclusion/exclusion it's not Since range types have extra information for their bound inclusion/exclusion it's not
...@@ -246,7 +255,7 @@ range.inclusive // [false, true] ...@@ -246,7 +255,7 @@ range.inclusive // [false, true]
Make sure you turn that into a serializable format before serialization since array Make sure you turn that into a serializable format before serialization since array
extra properties will not be serialized. extra properties will not be serialized.
#### Special Cases **Special Cases**
```js ```js
// empty range: // empty range:
......
...@@ -148,31 +148,18 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -148,31 +148,18 @@ class ConnectionManager extends AbstractConnectionManager {
} }
} }
// oids for hstore and geometry are dynamic - so select them at connection time if (query) {
const supportedVersion = this.sequelize.options.databaseVersion !== 0 && semver.gte(this.sequelize.options.databaseVersion, '8.3.0'); return connection.query(query);
if (dataTypes.HSTORE.types.postgres.oids.length === 0 && supportedVersion) { }
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\', \'geography\')'; }).tap(connection => {
if (
dataTypes.GEOGRAPHY.types.postgres.oids.length === 0 &&
dataTypes.GEOMETRY.types.postgres.oids.length === 0 &&
dataTypes.HSTORE.types.postgres.oids.length === 0 &&
dataTypes.ENUM.types.postgres.oids.length === 0
) {
return this._refreshDynamicOIDs(connection);
} }
return new Promise((resolve, reject) => connection.query(query, (error, result) => error ? reject(error) : resolve(result))).then(results => {
const result = Array.isArray(results) ? results.pop() : results;
for (const row of result.rows) {
let type;
if (row.typname === 'geometry') {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
} else if (row.typname === 'geography') {
type = dataTypes.postgres.GEOGRAPHY;
}
type.types.postgres.oids.push(row.oid);
type.types.postgres.array_oids.push(row.typarray);
this._refreshTypeParser(type);
}
});
}); });
} }
...@@ -186,6 +173,54 @@ class ConnectionManager extends AbstractConnectionManager { ...@@ -186,6 +173,54 @@ class ConnectionManager extends AbstractConnectionManager {
validate(connection) { validate(connection) {
return connection._invalid === undefined; return connection._invalid === undefined;
} }
_refreshDynamicOIDs(connection) {
const databaseVersion = this.sequelize.options.databaseVersion;
const supportedVersion = '8.3.0';
// Check for supported version
if ( (databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) {
return Promise.resolve();
}
// Refresh dynamic OIDs for some types
// These include, Geometry / HStore / Enum
return (connection || this.sequelize).query(
"SELECT typname, typtype, oid, typarray FROM pg_type WHERE (typtype = 'b' AND typname IN ('hstore', 'geometry', 'geography')) OR (typtype = 'e')"
).then(results => {
const result = Array.isArray(results) ? results.pop() : results;
// Reset OID mapping for dynamic type
[
dataTypes.postgres.GEOMETRY,
dataTypes.postgres.HSTORE,
dataTypes.postgres.GEOGRAPHY,
dataTypes.postgres.ENUM
].forEach(type => {
type.types.postgres.oids = [];
type.types.postgres.array_oids = [];
});
for (const row of result.rows) {
let type;
if (row.typname === 'geometry') {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
} else if (row.typname === 'geography') {
type = dataTypes.postgres.GEOGRAPHY;
} else if (row.typtype === 'e') {
type = dataTypes.postgres.ENUM;
}
type.types.postgres.oids.push(row.oid);
type.types.postgres.array_oids.push(row.typarray);
this._refreshTypeParser(type);
}
});
}
} }
_.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype); _.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);
......
...@@ -574,12 +574,37 @@ module.exports = BaseTypes => { ...@@ -574,12 +574,37 @@ module.exports = BaseTypes => {
}, this).join(',') + ']'; }, this).join(',') + ']';
if (this.type) { if (this.type) {
str += '::' + this.toSql(); const Utils = require('../../utils');
let castKey = this.toSql();
if (this.type instanceof BaseTypes.ENUM) {
castKey = Utils.addTicks(
Utils.generateEnumName(options.field.Model.getTableName(), options.field.fieldName),
'"'
) + '[]';
}
str += '::' + castKey;
} }
return str; return str;
}; };
function ENUM(options) {
if (!(this instanceof ENUM)) return new ENUM(options);
BaseTypes.ENUM.apply(this, arguments);
}
inherits(ENUM, BaseTypes.ENUM);
ENUM.parse = function(value) {
return value;
};
BaseTypes.ENUM.types.postgres = {
oids: [],
array_oids: []
};
const exports = { const exports = {
DECIMAL, DECIMAL,
BLOB, BLOB,
...@@ -598,7 +623,8 @@ module.exports = BaseTypes => { ...@@ -598,7 +623,8 @@ module.exports = BaseTypes => {
GEOMETRY, GEOMETRY,
GEOGRAPHY, GEOGRAPHY,
HSTORE, HSTORE,
RANGE RANGE,
ENUM
}; };
_.forIn(exports, (DataType, key) => { _.forIn(exports, (DataType, key) => {
......
...@@ -496,11 +496,24 @@ const QueryGenerator = { ...@@ -496,11 +496,24 @@ const QueryGenerator = {
} }
let type; let type;
if (attribute.type instanceof DataTypes.ENUM) { if (
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values; attribute.type instanceof DataTypes.ENUM ||
(attribute.type instanceof DataTypes.ARRAY && attribute.type.type instanceof DataTypes.ENUM)
) {
const enumType = attribute.type.type || attribute.type;
let values = attribute.values;
if (enumType.values && !attribute.values) {
values = enumType.values;
}
if (Array.isArray(values) && values.length > 0) {
type = 'ENUM(' + _.map(values, value => this.escape(value)).join(', ') + ')';
if (attribute.type instanceof DataTypes.ARRAY) {
type += '[]';
}
if (Array.isArray(attribute.values) && attribute.values.length > 0) {
type = 'ENUM(' + _.map(attribute.values, value => this.escape(value)).join(', ') + ')';
} else { } else {
throw new Error("Values for ENUM haven't been defined."); throw new Error("Values for ENUM haven't been defined.");
} }
...@@ -736,8 +749,9 @@ const QueryGenerator = { ...@@ -736,8 +749,9 @@ const QueryGenerator = {
pgEnumName(tableName, attr, options) { pgEnumName(tableName, attr, options) {
options = options || {}; options = options || {};
const tableDetails = this.extractTableDetails(tableName, options); const tableDetails = this.extractTableDetails(tableName, options);
let enumName = '"enum_' + tableDetails.tableName + '_' + attr + '"'; let enumName = Utils.addTicks(Utils.generateEnumName(tableDetails.tableName, attr), '"');
// pgListEnums requires the enum name only, without the schema // pgListEnums requires the enum name only, without the schema
if (options.schema !== false && tableDetails.schema) { if (options.schema !== false && tableDetails.schema) {
...@@ -745,7 +759,6 @@ const QueryGenerator = { ...@@ -745,7 +759,6 @@ const QueryGenerator = {
} }
return enumName; return enumName;
}, },
pgListEnums(tableName, attrName, options) { pgListEnums(tableName, attrName, options) {
......
...@@ -177,8 +177,14 @@ class QueryInterface { ...@@ -177,8 +177,14 @@ class QueryInterface {
const promises = []; const promises = [];
for (i = 0; i < keyLen; i++) { for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].type instanceof DataTypes.ENUM) { const attribute = attributes[keys[i]];
sql = this.QueryGenerator.pgListEnums(tableName, attributes[keys[i]].field || keys[i], options); const type = attribute.type;
if (
type instanceof DataTypes.ENUM ||
(type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM) //ARRAY sub type is ENUM
) {
sql = this.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options);
promises.push(this.sequelize.query( promises.push(this.sequelize.query(
sql, sql,
_.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT }) _.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT })
...@@ -191,17 +197,24 @@ class QueryInterface { ...@@ -191,17 +197,24 @@ class QueryInterface {
let enumIdx = 0; let enumIdx = 0;
for (i = 0; i < keyLen; i++) { for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].type instanceof DataTypes.ENUM) { const attribute = attributes[keys[i]];
const type = attribute.type;
const enumType = type.type || type;
if (
type instanceof DataTypes.ENUM ||
(type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM) //ARRAY sub type is ENUM
) {
// If the enum type doesn't exist then create it // If the enum type doesn't exist then create it
if (!results[enumIdx]) { if (!results[enumIdx]) {
sql = this.QueryGenerator.pgEnum(tableName, attributes[keys[i]].field || keys[i], attributes[keys[i]], options); sql = this.QueryGenerator.pgEnum(tableName, attribute.field || keys[i], enumType, options);
promises.push(this.sequelize.query( promises.push(this.sequelize.query(
sql, sql,
_.assign({}, options, { raw: true }) _.assign({}, options, { raw: true })
)); ));
} else if (!!results[enumIdx] && !!model) { } else if (!!results[enumIdx] && !!model) {
const enumVals = this.QueryGenerator.fromArray(results[enumIdx].enum_value); const enumVals = this.QueryGenerator.fromArray(results[enumIdx].enum_value);
const vals = model.rawAttributes[keys[i]].values; const vals = enumType.values;
vals.forEach((value, idx) => { vals.forEach((value, idx) => {
// reset out after/before options since it's for every enum value // reset out after/before options since it's for every enum value
...@@ -217,7 +230,7 @@ class QueryInterface { ...@@ -217,7 +230,7 @@ class QueryInterface {
valueOptions.after = vals[idx - 1]; valueOptions.after = vals[idx - 1];
} }
valueOptions.supportsSearchPath = false; valueOptions.supportsSearchPath = false;
promises.push(this.sequelize.query(this.QueryGenerator.pgEnumAdd(tableName, keys[i], value, valueOptions), valueOptions)); promises.push(this.sequelize.query(this.QueryGenerator.pgEnumAdd(tableName, attribute.field || keys[i], value, valueOptions), valueOptions));
} }
}); });
enumIdx++; enumIdx++;
...@@ -238,9 +251,19 @@ class QueryInterface { ...@@ -238,9 +251,19 @@ class QueryInterface {
}); });
sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); sql = this.QueryGenerator.createTableQuery(tableName, attributes, options);
return Promise.all(promises).then(() => { return Promise.all(promises)
return this.sequelize.query(sql, options); .tap(() => {
}); // If ENUM processed, then refresh OIDs
if (promises.length) {
return this.sequelize.dialect.connectionManager._refreshDynamicOIDs()
.then(() => {
return this.sequelize.refreshTypes(DataTypes.postgres);
});
}
})
.then(() => {
return this.sequelize.query(sql, options);
});
}); });
} else { } else {
if (!tableName.schema && if (!tableName.schema &&
......
...@@ -612,3 +612,17 @@ function isWhereEmpty(obj) { ...@@ -612,3 +612,17 @@ function isWhereEmpty(obj) {
return _.isEmpty(obj) && getOperators(obj).length === 0; return _.isEmpty(obj) && getOperators(obj).length === 0;
} }
exports.isWhereEmpty = isWhereEmpty; exports.isWhereEmpty = isWhereEmpty;
/**
* Returns ENUM name by joining table and column name
*
* @param {String} tableName
* @param {String} columnName
* @return {String}
* @private
*/
function generateEnumName(tableName, columnName) {
return 'enum_' + tableName + '_' + columnName;
}
exports.generateEnumName = generateEnumName;
...@@ -99,14 +99,15 @@ ...@@ -99,14 +99,15 @@
], ],
"main": "index", "main": "index",
"options": { "options": {
"env_cmd": "./test/config/.docker.env",
"mocha": "--globals setImmediate,clearImmediate --ui tdd --exit --check-leaks --colors -t 30000 --reporter spec" "mocha": "--globals setImmediate,clearImmediate --ui tdd --exit --check-leaks --colors -t 30000 --reporter spec"
}, },
"scripts": { "scripts": {
"lint": "eslint lib test --quiet", "lint": "eslint lib test --quiet",
"test": "npm run teaser && npm run test-unit && npm run test-integration", "test": "npm run teaser && npm run test-unit && npm run test-integration",
"test-docker": "npm run test-docker-unit && npm run test-docker-integration", "test-docker": "npm run test-docker-unit && npm run test-docker-integration",
"test-docker-unit": "env-cmd ./test/config/.docker.env npm run test-unit", "test-docker-unit": "npm run test-unit",
"test-docker-integration": "env-cmd ./test/config/.docker.env npm run test-integration", "test-docker-integration": "env-cmd $npm_package_options_env_cmd npm run test-integration",
"docs": "esdoc && cp docs/ROUTER esdoc/ROUTER", "docs": "esdoc && cp docs/ROUTER esdoc/ROUTER",
"teaser": "node -e \"console.log('#'.repeat(process.env.DIALECT.length + 22) + '\\n# Running tests for ' + process.env.DIALECT + ' #\\n' + '#'.repeat(process.env.DIALECT.length + 22))\"", "teaser": "node -e \"console.log('#'.repeat(process.env.DIALECT.length + 22) + '\\n# Running tests for ' + process.env.DIALECT + ' #\\n' + '#'.repeat(process.env.DIALECT.length + 22))\"",
"test-unit": "mocha $npm_package_options_mocha \"test/unit/**/*.js\"", "test-unit": "mocha $npm_package_options_mocha \"test/unit/**/*.js\"",
...@@ -132,10 +133,10 @@ ...@@ -132,10 +133,10 @@
"test-mssql": "cross-env DIALECT=mssql npm test", "test-mssql": "cross-env DIALECT=mssql npm test",
"test-all": "npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql", "test-all": "npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql",
"cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage",
"cover-integration": "cross-env COVERAGE=true node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -t 60000 --exit --ui tdd \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", "cover-integration": "cross-env COVERAGE=true ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -t 30000 --exit --ui tdd \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"",
"cover-unit": "cross-env COVERAGE=true node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -t 30000 --exit --ui tdd \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", "cover-unit": "cross-env COVERAGE=true ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -t 30000 --exit --ui tdd \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"",
"merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"",
"sscce": "env-cmd ./test/config/.docker.env node sscce.js", "sscce": "env-cmd $npm_package_options_env_cmd node sscce.js",
"sscce-mysql": "cross-env DIALECT=mysql npm run sscce", "sscce-mysql": "cross-env DIALECT=mysql npm run sscce",
"sscce-postgres": "cross-env DIALECT=postgres npm run sscce", "sscce-postgres": "cross-env DIALECT=postgres npm run sscce",
"sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce", "sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce",
......
...@@ -266,8 +266,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -266,8 +266,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
it('calls parse and stringify for ENUM', () => { it('calls parse and stringify for ENUM', () => {
const Type = new Sequelize.ENUM('hat', 'cat'); const Type = new Sequelize.ENUM('hat', 'cat');
// No dialects actually allow us to identify that we get an enum back.. if (['postgres'].indexOf(dialect) !== -1) {
testFailure(Type); return testSuccess(Type, 'hat');
} else {
testFailure(Type);
}
}); });
if (current.dialect.supports.GEOMETRY) { if (current.dialect.supports.GEOMETRY) {
......
...@@ -315,6 +315,38 @@ if (dialect.match(/^postgres/)) { ...@@ -315,6 +315,38 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should be able to create/drop multiple enums multiple times with field name (#7812)', function() {
const DummyModel = this.sequelize.define('Dummy-pg', {
username: DataTypes.STRING,
theEnumOne: {
field: 'oh_my_this_enum_one',
type: DataTypes.ENUM,
values: [
'one',
'two',
'three'
]
},
theEnumTwo: {
field: 'oh_my_this_enum_two',
type: DataTypes.ENUM,
values: [
'four',
'five',
'six'
]
}
});
return DummyModel.sync({ force: true }).then(() => {
// now sync one more time:
return DummyModel.sync({ force: true }).then(() => {
// sync without dropping
return DummyModel.sync();
});
});
});
it('should be able to add values to enum types', function() { it('should be able to add values to enum types', function() {
let User = this.sequelize.define('UserEnums', { let User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh') mood: DataTypes.ENUM('happy', 'sad', 'meh')
...@@ -333,6 +365,161 @@ if (dialect.match(/^postgres/)) { ...@@ -333,6 +365,161 @@ if (dialect.match(/^postgres/)) {
expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}'); expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}');
}); });
}); });
describe('ARRAY(ENUM)', () => {
it('should be able to ignore enum types that already exist', function() {
const User = this.sequelize.define('UserEnums', {
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return User.sync({ force: true }).then(() => User.sync());
});
it('should be able to create/drop enums multiple times', function() {
const User = this.sequelize.define('UserEnums', {
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return User.sync({ force: true }).then(() => User.sync({ force: true }));
});
it('should be able to add values to enum types', function() {
let User = this.sequelize.define('UserEnums', {
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return User.sync({ force: true }).then(() => {
User = this.sequelize.define('UserEnums', {
permissions: DataTypes.ARRAY(
DataTypes.ENUM('view', 'access', 'edit', 'write', 'check', 'delete')
)
});
return User.sync();
}).then(() => {
return this.sequelize.getQueryInterface().pgListEnums(User.getTableName());
}).then(enums => {
expect(enums).to.have.length(1);
expect(enums[0].enum_value).to.equal('{view,access,edit,write,check,delete}');
});
});
it('should be able to insert new record', function() {
const User = this.sequelize.define('UserEnums', {
name: DataTypes.STRING,
type: DataTypes.ENUM('A', 'B', 'C'),
owners: DataTypes.ARRAY(DataTypes.STRING),
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return User.sync({ force: true })
.then(() => {
return User.create({
name: 'file.exe',
type: 'C',
owners: ['userA', 'userB'],
permissions: ['access', 'write']
});
})
.then(user => {
expect(user.name).to.equal('file.exe');
expect(user.type).to.equal('C');
expect(user.owners).to.deep.equal(['userA', 'userB']);
expect(user.permissions).to.deep.equal(['access', 'write']);
});
});
it('should fail when trying to insert foreign element on ARRAY(ENUM)', function() {
const User = this.sequelize.define('UserEnums', {
name: DataTypes.STRING,
type: DataTypes.ENUM('A', 'B', 'C'),
owners: DataTypes.ARRAY(DataTypes.STRING),
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return expect(User.sync({ force: true }).then(() => {
return User.create({
name: 'file.exe',
type: 'C',
owners: ['userA', 'userB'],
permissions: ['cosmic_ray_disk_access']
});
})).to.be.rejectedWith(/invalid input value for enum "enum_UserEnums_permissions": "cosmic_ray_disk_access"/);
});
it('should be able to find records', function() {
const User = this.sequelize.define('UserEnums', {
name: DataTypes.STRING,
type: DataTypes.ENUM('A', 'B', 'C'),
permissions: DataTypes.ARRAY(DataTypes.ENUM([
'access',
'write',
'check',
'delete'
]))
});
return User.sync({ force: true })
.then(() => {
return User.bulkCreate([{
name: 'file1.exe',
type: 'C',
permissions: ['access', 'write']
}, {
name: 'file2.exe',
type: 'A',
permissions: ['access', 'check']
}, {
name: 'file3.exe',
type: 'B',
permissions: ['access', 'write', 'delete']
}]);
})
.then(() => {
return User.findAll({
where: {
type: {
$in: ['A', 'C']
},
permissions: {
$contains: ['write']
}
}
});
})
.then(users => {
expect(users.length).to.equal(1);
expect(users[0].name).to.equal('file1.exe');
expect(users[0].type).to.equal('C');
expect(users[0].permissions).to.deep.equal(['access', 'write']);
});
});
});
}); });
describe('integers', () => { describe('integers', () => {
......
'use strict'; 'use strict';
const Support = require('../support'), const Support = require('../support');
dialect = Support.getTestDialect();
before(() => {
if (dialect !== 'postgres' && dialect !== 'postgres-native') {
return;
}
return Support.sequelize.Promise.all([
Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', {raw: true}),
Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS btree_gist', {raw: true})
]);
});
beforeEach(function() { beforeEach(function() {
this.sequelize.test.trackRunningQueries(); this.sequelize.test.trackRunningQueries();
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!