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

Commit 75841f3e by Youngrok Kim Committed by Sushant

SQLite JSON datatype support (with JSON1 extension) (#7094)

* Adds JSON support for sqlite

* Add JSON support to sqlite query-generator

* add integration test for sqlite json

* add unit test for sqlite json

* fix inaccurate test for json from postgres

* Fix failing test spec in postgres

* Change accroding to review

- Change MySqlQueryGenerator to AbstractQueryGenerator (sqlite)
- Move parseConditionObject method to the AbstractQueryGenerator (sqlite, postgres)
- Remove unnecessary module require
- Remove extra lines

* Fix failing test with sequelize static reference

* Add JSONB type alias to sqlite data types

* Update changelog.md

* Update jsdoc for JSON datatype

* Updates from PR feedback

- Convert es5 syntax to es6 for sqlite query-generator

* Fix parenthesis with single parameter

* Fix deleteQuery condition with JSON field

* Update integration/data-types.test.js

* Add JSON injection preventation code for sqlite

- Add checkValidJsonStatement method to sqlite query-generator
- Add injection tests for sqlite

* Update sqlite JSON query-generator

* Move common json DAO tests to abstract/dao.test.js

- Add support for json property accessors (sqlite, postgres)

* Fix wrong indentation in abstract/query-generator.js

* Update query-generator.js to use common json method

* Fix inconsistent postgres cast syntax

* Update JSON test specs

- Add injection test for postgres/query-generators.js
- Replace default sql test expectation with specific dialect (postgres)
- Update expectsql from test/support.js to throw error when no default expectation

* Fix failing postgres test with cast

* Fix postgres casting syntax

* Fix failing test for postgres

* Revert indentation of postgres query-generator

* Cleanup unnecessarily added code

update jsdoc of jsonPathExtractionQuery to match the exact function arguments

* Move integration/dialects/abstract/dao.test.js to integration/json.test.js

* Rewrite unit tests for json query-generator in unit/sql/json.test.js

* Capitalize AND operator while generating json query via condition object

* Fix failed tests
1 parent 0b215062
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
- [FIXED] MSSQL tedious debug regression fix when dialectOptions are not passed [#7130](https://github.com/sequelize/sequelize/pull/7130) - [FIXED] MSSQL tedious debug regression fix when dialectOptions are not passed [#7130](https://github.com/sequelize/sequelize/pull/7130)
- [CHANGED] `setIsolationLevelQuery` to skip under MSSQL dialect, added debug listener for tedious [#7130](https://github.com/sequelize/sequelize/pull/7130) - [CHANGED] `setIsolationLevelQuery` to skip under MSSQL dialect, added debug listener for tedious [#7130](https://github.com/sequelize/sequelize/pull/7130)
- [FIXED] `sourceKey` FOR `hasMany` now also works if a `where` was specified in an `include` [#7141](https://github.com/sequelize/sequelize/issues/7141) - [FIXED] `sourceKey` FOR `hasMany` now also works if a `where` was specified in an `include` [#7141](https://github.com/sequelize/sequelize/issues/7141)
- [ADDED] SQLite JSON datatype support [#7094](https://github.com/sequelize/sequelize/pull/7094)
- [FIXED] `removeColumn` method to support dropping primaryKey column (MSSQL) [#7081](https://github.com/sequelize/sequelize/pull/7081) - [FIXED] `removeColumn` method to support dropping primaryKey column (MSSQL) [#7081](https://github.com/sequelize/sequelize/pull/7081)
- [ADDED] Filtered Indexes support for SQL Server [#7016](https://github.com/sequelize/sequelize/issues/7016) - [ADDED] Filtered Indexes support for SQL Server [#7016](https://github.com/sequelize/sequelize/issues/7016)
- [FIXED] Set `timestamps` and `paranoid` options from through model on `belongsToMany` association - [FIXED] Set `timestamps` and `paranoid` options from through model on `belongsToMany` association
......
...@@ -502,7 +502,7 @@ HSTORE.prototype.validate = function validate(value) { ...@@ -502,7 +502,7 @@ HSTORE.prototype.validate = function validate(value) {
}; };
/** /**
* A JSON string column. Only available in postgres. * A JSON string column. Only available in postgres and sqlite.
* *
* @function JSON * @function JSON
* @memberof DataTypes * @memberof DataTypes
......
...@@ -1749,6 +1749,7 @@ const QueryGenerator = { ...@@ -1749,6 +1749,7 @@ const QueryGenerator = {
} }
return ''; return '';
}, },
whereItemsQuery(where, options, binding) { whereItemsQuery(where, options, binding) {
if ( if (
(Array.isArray(where) && where.length === 0) || (Array.isArray(where) && where.length === 0) ||
...@@ -1779,6 +1780,7 @@ const QueryGenerator = { ...@@ -1779,6 +1780,7 @@ const QueryGenerator = {
return items.length && items.filter(item => item && item.length).join(binding) || ''; return items.length && items.filter(item => item && item.length).join(binding) || '';
}, },
whereItemQuery(key, value, options) { whereItemQuery(key, value, options) {
options = options || {}; options = options || {};
...@@ -1947,17 +1949,17 @@ const QueryGenerator = { ...@@ -1947,17 +1949,17 @@ const QueryGenerator = {
path[path.length - 1] = tmp[0]; path[path.length - 1] = tmp[0];
} }
let baseKey = this.quoteIdentifier(key)+'#>>\'{'+path.join(', ')+'}\''; let baseKey = this.quoteIdentifier(key);
if (options.prefix) { if (options.prefix) {
if (options.prefix instanceof Utils.Literal) { if (options.prefix instanceof Utils.Literal) {
baseKey = this.handleSequelizeMethod(options.prefix)+'.'+baseKey; baseKey = `${this.handleSequelizeMethod(options.prefix)}.${baseKey}`;
} else { } else {
baseKey = this.quoteTable(options.prefix)+'.'+baseKey; baseKey = `${this.quoteTable(options.prefix)}.${baseKey}`;
} }
} }
baseKey = '('+baseKey+')'; baseKey = this.jsonPathExtractionQuery(baseKey, path);
const castKey = item => { const castKey = item => {
let key = baseKey; let key = baseKey;
...@@ -1973,7 +1975,7 @@ const QueryGenerator = { ...@@ -1973,7 +1975,7 @@ const QueryGenerator = {
} }
if (cast) { if (cast) {
key += '::'+cast; return this.handleSequelizeMethod(new Utils.Cast(new Utils.Literal(key), cast));
} }
return key; return key;
...@@ -2233,6 +2235,38 @@ const QueryGenerator = { ...@@ -2233,6 +2235,38 @@ const QueryGenerator = {
return result ? result : '1=1'; return result ? result : '1=1';
}, },
// A recursive parser for nested where conditions
parseConditionObject(conditions, path) {
path = path || [];
return _.reduce(conditions, (result, value, key) => {
if (_.isObject(value)) {
result = result.concat(this.parseConditionObject(value, path.concat(key))); // Recursively parse objects
} else {
result.push({ path: path.concat(key), value: value });
}
return result;
}, []);
},
/**
* Generates an SQL query that extract JSON property of given path.
*
* @param {String} column The JSON column
* @param {String|Array<String>} [path] The path to extract (optional)
* @returns {String} The generated sql query
* @private
*/
jsonPathExtractionQuery(column, path) {
const paths = _.toPath(path);
const pathStr = `{${paths.join(',')}}`;
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `${quotedColumn}#>>'${pathStr}'`;
},
isIdentifierQuoted(string) {
return /^\s*(?:([`"'])(?:(?!\1).|\1{2})*\1\.?)+\s*$/i.test(string);
},
booleanValue(value) { booleanValue(value) {
return value; return value;
} }
......
...@@ -63,7 +63,7 @@ const QueryGenerator = { ...@@ -63,7 +63,7 @@ const QueryGenerator = {
const values = { const values = {
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
attributes: attrStr.join(', '), attributes: attrStr.join(', '),
comments: Utils._.template(comments)({ table: this.quoteTable(tableName)}) comments: Utils._.template(comments)({ table: this.quoteTable(tableName) })
}; };
if (!!options.uniqueKeys) { if (!!options.uniqueKeys) {
...@@ -121,17 +121,86 @@ const QueryGenerator = { ...@@ -121,17 +121,86 @@ const QueryGenerator = {
`WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `; `WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `;
}, },
// A recursive parser for nested where conditions /**
parseConditionObject(_conditions, path) { * Check whether the statmement is json function or simple path
path = path || []; *
return Utils._.reduce(_conditions, (r, v, k) => { // result, key, value * @param {String} stmt The statement to validate
if (Utils._.isObject(v)) { * @returns {Boolean} true if the given statement is json function
r = r.concat(this.parseConditionObject(v, path.concat(k))); // Recursively parse objects * @throws {Error} throw if the statement looks like json function but has invalid token
} else { */
r.push({ path: path.concat(k), value: v }); checkValidJsonStatement(stmt) {
if (!_.isString(stmt)) {
return false;
}
// https://www.postgresql.org/docs/current/static/functions-json.html
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const jsonOperatorRegex = /^\s*(->>?|#>>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
let currentIndex = 0;
let openingBrackets = 0;
let closingBrackets = 0;
let hasJsonFunction = false;
let hasInvalidToken = false;
while (currentIndex < stmt.length) {
const string = stmt.substr(currentIndex);
const functionMatches = jsonFunctionRegex.exec(string);
if (functionMatches) {
currentIndex += functionMatches[0].indexOf('(');
hasJsonFunction = true;
continue;
}
const operatorMatches = jsonOperatorRegex.exec(string);
if (operatorMatches) {
currentIndex += operatorMatches[0].length;
hasJsonFunction = true;
continue;
} }
return r;
}, []); const tokenMatches = tokenCaptureRegex.exec(string);
if (tokenMatches) {
const capturedToken = tokenMatches[1];
if (capturedToken === '(') {
openingBrackets++;
} else if (capturedToken === ')') {
closingBrackets++;
} else if (capturedToken === ';') {
hasInvalidToken = true;
break;
}
currentIndex += tokenMatches[0].length;
continue;
}
break;
}
// Check invalid json statement
hasInvalidToken |= openingBrackets !== closingBrackets;
if (hasJsonFunction && hasInvalidToken) {
throw new Error('Invalid json statement: ' + stmt);
}
// return true if the statement has valid json function
return hasJsonFunction;
},
/**
* Generates an SQL query that extract JSON property of given path.
*
* @param {String} column The JSON column
* @param {String|Array<String>} [path] The path to extract (optional)
* @returns {String} The generated sql query
* @private
*/
jsonPathExtractionQuery(column, path) {
const paths = _.toPath(path);
const pathStr = `{${paths.join(',')}}`;
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `(${quotedColumn}#>>'${pathStr}')`;
}, },
handleSequelizeMethod(smth, tableName, factory, options, prepend) { handleSequelizeMethod(smth, tableName, factory, options, prepend) {
...@@ -139,20 +208,21 @@ const QueryGenerator = { ...@@ -139,20 +208,21 @@ const QueryGenerator = {
// Parse nested object // Parse nested object
if (smth.conditions) { if (smth.conditions) {
const conditions = _.map(this.parseConditionObject(smth.conditions), condition => const conditions = _.map(this.parseConditionObject(smth.conditions), condition =>
`${this.quoteIdentifier(_.first(condition.path))}#>>'{${_.tail(condition.path).join(',')}}' = '${condition.value}'` `${this.jsonPathExtractionQuery(_.first(condition.path), _.tail(condition.path))} = '${condition.value}'`
); );
return conditions.join(' and '); return conditions.join(' AND ');
} else if (smth.path) { } else if (smth.path) {
let str; let str;
// Allow specifying conditions using the postgres json syntax // Allow specifying conditions using the postgres json syntax
if (_.some(['->', '->>', '#>'], _.partial(_.includes, smth.path))) { if (this.checkValidJsonStatement(smth.path)) {
str = smth.path; str = smth.path;
} else { } else {
// Also support json dot notation // Also support json property accessors
const path = smth.path.split('.'); const paths = _.toPath(smth.path);
str = `${this.quoteIdentifier(_.first(path))}#>>'{${_.tail(path).join(',')}}'`; const column = paths.shift();
str = this.jsonPathExtractionQuery(column, paths);
} }
if (smth.value) { if (smth.value) {
...@@ -161,14 +231,13 @@ const QueryGenerator = { ...@@ -161,14 +231,13 @@ const QueryGenerator = {
return str; return str;
} }
} else {
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
} }
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
}, },
addColumnQuery(table, key, dataType) { addColumnQuery(table, key, dataType) {
const dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'}); const dbDataType = this.attributeToSQL(dataType, { context: 'addColumn' });
const definition = this.dataTypeMapping(table, key, dbDataType); const definition = this.dataTypeMapping(table, key, dbDataType);
const quotedKey = this.quoteIdentifier(key); const quotedKey = this.quoteIdentifier(key);
const quotedTable = this.quoteTable(this.extractTableDetails(table)); const quotedTable = this.quoteTable(this.extractTableDetails(table));
...@@ -281,7 +350,7 @@ const QueryGenerator = { ...@@ -281,7 +350,7 @@ const QueryGenerator = {
}, },
exceptionFn(fnName, tableName, main, then, when, returns, language) { exceptionFn(fnName, tableName, main, then, when, returns, language) {
when = when || 'unique_violation'; when = when || 'unique_violation';
const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`; const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`;
...@@ -605,7 +674,6 @@ const QueryGenerator = { ...@@ -605,7 +674,6 @@ const QueryGenerator = {
return paramList.join(', '); return paramList.join(', ');
}, },
expandOptions(options) { expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ? return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t'); '' : '\n\t' + options.join('\n\t');
...@@ -779,8 +847,7 @@ const QueryGenerator = { ...@@ -779,8 +847,7 @@ const QueryGenerator = {
} }
}, },
/* /**
/**
* Generates an SQL query that returns all foreign keys of a table. * Generates an SQL query that returns all foreign keys of a table.
* *
* @param {String} tableName The name of the table. * @param {String} tableName The name of the table.
...@@ -805,7 +872,6 @@ const QueryGenerator = { ...@@ -805,7 +872,6 @@ const QueryGenerator = {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'; return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';';
}, },
setAutocommitQuery(value, options) { setAutocommitQuery(value, options) {
if (options.parent) { if (options.parent) {
return; return;
......
...@@ -23,6 +23,17 @@ module.exports = BaseTypes => { ...@@ -23,6 +23,17 @@ module.exports = BaseTypes => {
BaseTypes.REAL.types.sqlite = ['REAL']; BaseTypes.REAL.types.sqlite = ['REAL'];
BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION']; BaseTypes.DOUBLE.types.sqlite = ['DOUBLE PRECISION'];
BaseTypes.GEOMETRY.types.sqlite = false; BaseTypes.GEOMETRY.types.sqlite = false;
BaseTypes.JSON.types.sqlite = ['JSON', 'JSONB'];
function JSONTYPE() {
if (!(this instanceof JSONTYPE)) return new JSONTYPE();
BaseTypes.JSON.apply(this, arguments);
}
inherits(JSONTYPE, BaseTypes.JSON);
JSONTYPE.parse = function parse(data) {
return JSON.parse(data);
};
function DATE(length) { function DATE(length) {
if (!(this instanceof DATE)) return new DATE(length); if (!(this instanceof DATE)) return new DATE(length);
...@@ -205,7 +216,8 @@ module.exports = BaseTypes => { ...@@ -205,7 +216,8 @@ module.exports = BaseTypes => {
INTEGER, INTEGER,
BIGINT, BIGINT,
TEXT, TEXT,
ENUM ENUM,
JSON: JSONTYPE
}; };
_.forIn(exports, (DataType, key) => { _.forIn(exports, (DataType, key) => {
......
...@@ -34,7 +34,8 @@ SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype ...@@ -34,7 +34,8 @@ SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype
}, },
joinTableDependent: false, joinTableDependent: false,
groupedLimit: false, groupedLimit: false,
ignoreDuplicates: ' OR IGNORE' ignoreDuplicates: ' OR IGNORE',
JSON: true
}); });
ConnectionManager.prototype.defaultVersion = '3.8.0'; ConnectionManager.prototype.defaultVersion = '3.8.0';
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
/* jshint -W110 */ /* jshint -W110 */
const Utils = require('../../utils'); const Utils = require('../../utils');
const util = require('util');
const Transaction = require('../../transaction'); const Transaction = require('../../transaction');
const _ = require('lodash'); const _ = require('lodash');
const MySqlQueryGenerator = require('../mysql/query-generator'); const MySqlQueryGenerator = require('../mysql/query-generator');
const AbstractQueryGenerator = require('../abstract/query-generator');
const QueryGenerator = { const QueryGenerator = {
/* jshint proto:true */ /* jshint proto:true */
...@@ -75,15 +77,128 @@ const QueryGenerator = { ...@@ -75,15 +77,128 @@ const QueryGenerator = {
return this.replaceBooleanDefaults(sql); return this.replaceBooleanDefaults(sql);
}, },
booleanValue(value){ booleanValue(value) {
return !!value ? 1 : 0; return !!value ? 1 : 0;
}, },
addColumnQuery(table, key, dataType) { /**
* Check whether the statmement is json function or simple path
*
* @param {String} stmt The statement to validate
* @returns {Boolean} true if the given statement is json function
* @throws {Error} throw if the statement looks like json function but has invalid token
*/
checkValidJsonStatement(stmt) {
if (!_.isString(stmt)) {
return false;
}
// https://sqlite.org/json1.html
const jsonFunctionRegex = /^\s*(json(?:_[a-z]+){0,2})\([^)]*\)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
let currentIndex = 0;
let openingBrackets = 0;
let closingBrackets = 0;
let hasJsonFunction = false;
let hasInvalidToken = false;
while (currentIndex < stmt.length) {
const string = stmt.substr(currentIndex);
const functionMatches = jsonFunctionRegex.exec(string);
if (functionMatches) {
currentIndex += functionMatches[0].indexOf('(');
hasJsonFunction = true;
continue;
}
const tokenMatches = tokenCaptureRegex.exec(string);
if (tokenMatches) {
const capturedToken = tokenMatches[1];
if (capturedToken === '(') {
openingBrackets++;
} else if (capturedToken === ')') {
closingBrackets++;
} else if (capturedToken === ';') {
hasInvalidToken = true;
break;
}
currentIndex += tokenMatches[0].length;
continue;
}
break;
}
// Check invalid json statement
hasInvalidToken |= openingBrackets !== closingBrackets;
if (hasJsonFunction && hasInvalidToken) {
throw new Error('Invalid json statement: ' + stmt);
}
// return true if the statement has valid json function
return hasJsonFunction;
},
/**
* Generates an SQL query that extract JSON property of given path.
*
* @param {String} column The JSON column
* @param {String|Array<String>} [path] The path to extract (optional)
* @returns {String} The generated sql query
* @private
*/
jsonPathExtractionQuery(column, path) {
const paths = _.toPath(path);
const pathStr = ['$']
.concat(paths)
.join('.')
.replace(/\.(\d+)(?:(?=\.)|$)/g, (_, digit) => `[${digit}]`);
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `json_extract(${quotedColumn}, '${pathStr}')`;
},
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
// Parse nested object
if (smth.conditions) {
const conditions = this.parseConditionObject(smth.conditions).map(condition =>
`${this.jsonPathExtractionQuery(_.first(condition.path), _.tail(condition.path))} = '${condition.value}'`
);
return conditions.join(' AND ');
} else if (smth.path) {
let str;
// Allow specifying conditions using the sqlite json functions
if (this.checkValidJsonStatement(smth.path)) {
str = smth.path;
} else {
// Also support json property accessors
const paths = _.toPath(smth.path);
const column = paths.shift();
str = this.jsonPathExtractionQuery(column, paths);
}
if (smth.value) {
str += util.format(' = %s', this.escape(smth.value));
}
return str;
}
} else if (smth instanceof Utils.Cast) {
if (/timestamp/i.test(smth.type)) {
smth.type = 'datetime';
}
}
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
},
addColumnQuery(table, key, dataType) {
const attributes = {}; const attributes = {};
attributes[key] = dataType; attributes[key] = dataType;
const fields = this.attributesToSQL(attributes, {context: 'addColumn'}); const fields = this.attributesToSQL(attributes, { context: 'addColumn' });
const attribute = this.quoteIdentifier(key) + ' ' + fields[key]; const attribute = this.quoteIdentifier(key) + ' ' + fields[key];
const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`; const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`;
...@@ -129,10 +244,11 @@ const QueryGenerator = { ...@@ -129,10 +244,11 @@ const QueryGenerator = {
return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where)}`; return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where)}`;
}, },
deleteQuery(tableName, where, options) { deleteQuery(tableName, where, options, model) {
options = options || {}; options = options || {};
_.defaults(options, this.options);
let whereClause = this.getWhereConditions(where); let whereClause = this.getWhereConditions(where, null, model, options);
if (whereClause) { if (whereClause) {
whereClause = ' WHERE ' + whereClause; whereClause = ' WHERE ' + whereClause;
} }
...@@ -173,11 +289,11 @@ const QueryGenerator = { ...@@ -173,11 +289,11 @@ const QueryGenerator = {
} }
} }
if(dataType.references) { if (dataType.references) {
const referencesTable = this.quoteTable(dataType.references.model); const referencesTable = this.quoteTable(dataType.references.model);
let referencesKey; let referencesKey;
if(dataType.references.key) { if (dataType.references.key) {
referencesKey = this.quoteIdentifier(dataType.references.key); referencesKey = this.quoteIdentifier(dataType.references.key);
} else { } else {
referencesKey = this.quoteIdentifier('id'); referencesKey = this.quoteIdentifier('id');
...@@ -185,11 +301,11 @@ const QueryGenerator = { ...@@ -185,11 +301,11 @@ const QueryGenerator = {
sql += ` REFERENCES ${referencesTable} (${referencesKey})`; sql += ` REFERENCES ${referencesTable} (${referencesKey})`;
if(dataType.onDelete) { if (dataType.onDelete) {
sql += ' ON DELETE ' + dataType.onDelete.toUpperCase(); sql += ' ON DELETE ' + dataType.onDelete.toUpperCase();
} }
if(dataType.onUpdate) { if (dataType.onUpdate) {
sql += ' ON UPDATE ' + dataType.onUpdate.toUpperCase(); sql += ' ON UPDATE ' + dataType.onUpdate.toUpperCase();
} }
...@@ -303,7 +419,7 @@ const QueryGenerator = { ...@@ -303,7 +419,7 @@ const QueryGenerator = {
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';'; return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
} }
return 'BEGIN ' + transaction.options.type + ' TRANSACTION;'; return 'BEGIN ' + transaction.options.type + ' TRANSACTION;';
}, },
setAutocommitQuery() { setAutocommitQuery() {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* jshint -W030 */ /* jshint -W030 */
/* jshint -W110 */ /* jshint -W110 */
var chai = require('chai') const chai = require('chai')
, Sequelize = require('../../index') , Sequelize = require('../../index')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
...@@ -15,24 +15,25 @@ var chai = require('chai') ...@@ -15,24 +15,25 @@ var chai = require('chai')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, semver = require('semver'); , semver = require('semver');
describe(Support.getTestDialectTeaser('DataTypes'), function() { describe(Support.getTestDialectTeaser('DataTypes'), function () {
afterEach(function () { afterEach(function () {
// Restore some sanity by resetting all parsers // Restore some sanity by resetting all parsers
switch (dialect) { switch (dialect) {
case 'postgres': case 'postgres':
var types = require('pg-types'); const types = require('pg-types');
_.each(DataTypes, function (dataType) { _.each(DataTypes, dataType => {
if (dataType.types && dataType.types.postgres) { if (dataType.types && dataType.types.postgres) {
dataType.types.postgres.oids.forEach(function (oid) { dataType.types.postgres.oids.forEach(oid => {
types.setTypeParser(oid, _.identity); types.setTypeParser(oid, _.identity);
}); });
} }
}); });
require('pg-types/lib/binaryParsers').init(function (oid, converter) {
require('pg-types/lib/binaryParsers').init((oid, converter) => {
types.setTypeParser(oid, 'binary', converter); types.setTypeParser(oid, 'binary', converter);
}); });
require('pg-types/lib/textParsers').init(function (oid, converter) { require('pg-types/lib/textParsers').init((oid, converter) => {
types.setTypeParser(oid, 'text', converter); types.setTypeParser(oid, 'text', converter);
}); });
break; break;
...@@ -43,12 +44,12 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -43,12 +44,12 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers
}); });
it('allows me to return values from a custom parse function', function () { it('allows me to return values from a custom parse function', () => {
var parse = Sequelize.DATE.parse = sinon.spy(function (value) { const parse = Sequelize.DATE.parse = sinon.spy(function (value) {
return moment(value, 'YYYY-MM-DD HH:mm:ss'); return moment(value, 'YYYY-MM-DD HH:mm:ss');
}); });
var stringify = Sequelize.DATE.prototype.stringify = sinon.spy(function (value, options) { const stringify = Sequelize.DATE.prototype.stringify = sinon.spy(function (value, options) {
if (!moment.isMoment(value)) { if (!moment.isMoment(value)) {
value = this._applyTimezone(value, options); value = this._applyTimezone(value, options);
} }
...@@ -57,19 +58,19 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -57,19 +58,19 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
current.refreshTypes(); current.refreshTypes();
var User = current.define('user', { const User = current.define('user', {
dateField: Sequelize.DATE dateField: Sequelize.DATE
}, { }, {
timestamps: false timestamps: false
}); });
return current.sync({ force: true }).then(function () { return current.sync({ force: true }).then(() => {
return User.create({ return User.create({
dateField: moment("2011 10 31", 'YYYY MM DD') dateField: moment("2011 10 31", 'YYYY MM DD')
}); });
}).then(function () { }).then(() => {
return User.findAll().get(0); return User.findAll().get(0);
}).then(function (user) { }).then(user => {
expect(parse).to.have.been.called; expect(parse).to.have.been.called;
expect(stringify).to.have.been.called; expect(stringify).to.have.been.called;
...@@ -79,30 +80,30 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -79,30 +80,30 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
}); });
var testSuccess = function (Type, value) { const testSuccess = function (Type, value) {
var parse = Type.constructor.parse = sinon.spy(function (value) { const parse = Type.constructor.parse = sinon.spy(function (value) {
return value; return value;
}); });
var stringify = Type.constructor.prototype.stringify = sinon.spy(function (value) { const stringify = Type.constructor.prototype.stringify = sinon.spy(function (value) {
return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments); return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments);
}); });
current.refreshTypes(); current.refreshTypes();
var User = current.define('user', { const User = current.define('user', {
field: Type field: Type
}, { }, {
timestamps: false timestamps: false
}); });
return current.sync({ force: true }).then(function () { return current.sync({ force: true }).then(() => {
return User.create({ return User.create({
field: value field: value
}); });
}).then(function () { }).then(() => {
return User.findAll().get(0); return User.findAll().get(0);
}).then(function (user) { }).then(user => {
expect(parse).to.have.been.called; expect(parse).to.have.been.called;
expect(stringify).to.have.been.called; expect(stringify).to.have.been.called;
...@@ -111,7 +112,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -111,7 +112,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
}; };
var testFailure = function (Type, value) { const testFailure = function (Type, value) {
Type.constructor.parse = _.noop(); Type.constructor.parse = _.noop();
expect(function () { expect(function () {
...@@ -121,70 +122,76 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -121,70 +122,76 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
delete Type.constructor.parse; delete Type.constructor.parse;
}; };
if (dialect === 'postgres') { if (current.dialect.supports.JSON) {
it('calls parse and stringify for JSON', function () { it('calls parse and stringify for JSON', function () {
var Type = new Sequelize.JSON(); const Type = new Sequelize.JSON();
return testSuccess(Type, { test: 42, nested: { foo: 'bar' }}); return testSuccess(Type, { test: 42, nested: { foo: 'bar' }});
}); });
}
if (current.dialect.supports.JSONB) {
it('calls parse and stringify for JSONB', function () { it('calls parse and stringify for JSONB', function () {
var Type = new Sequelize.JSONB(); const Type = new Sequelize.JSONB();
return testSuccess(Type, { test: 42, nested: { foo: 'bar' }}); return testSuccess(Type, { test: 42, nested: { foo: 'bar' }});
}); });
}
if (current.dialect.supports.HSTORE) {
it('calls parse and stringify for HSTORE', function () { it('calls parse and stringify for HSTORE', function () {
var Type = new Sequelize.HSTORE(); const Type = new Sequelize.HSTORE();
return testSuccess(Type, { test: 42, nested: false }); return testSuccess(Type, { test: 42, nested: false });
}); });
}
if (current.dialect.supports.RANGE) {
it('calls parse and stringify for RANGE', function () { it('calls parse and stringify for RANGE', function () {
var Type = new Sequelize.RANGE(new Sequelize.INTEGER()); const Type = new Sequelize.RANGE(new Sequelize.INTEGER());
return testSuccess(Type, [1, 2]); return testSuccess(Type, [1, 2]);
}); });
} }
it('calls parse and stringify for DATE', function () { it('calls parse and stringify for DATE', function () {
var Type = new Sequelize.DATE(); const Type = new Sequelize.DATE();
return testSuccess(Type, new Date()); return testSuccess(Type, new Date());
}); });
it('calls parse and stringify for DATEONLY', function () { it('calls parse and stringify for DATEONLY', function () {
var Type = new Sequelize.DATEONLY(); const Type = new Sequelize.DATEONLY();
return testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); return testSuccess(Type, moment(new Date()).format('YYYY-MM-DD'));
}); });
it('calls parse and stringify for TIME', function () { it('calls parse and stringify for TIME', function () {
var Type = new Sequelize.TIME(); const Type = new Sequelize.TIME();
return testSuccess(Type, new Date()); return testSuccess(Type, new Date());
}); });
it('calls parse and stringify for BLOB', function () { it('calls parse and stringify for BLOB', function () {
var Type = new Sequelize.BLOB(); const Type = new Sequelize.BLOB();
return testSuccess(Type, 'foobar'); return testSuccess(Type, 'foobar');
}); });
it('calls parse and stringify for CHAR', function () { it('calls parse and stringify for CHAR', function () {
var Type = new Sequelize.CHAR(); const Type = new Sequelize.CHAR();
return testSuccess(Type, 'foobar'); return testSuccess(Type, 'foobar');
}); });
it('calls parse and stringify for STRING', function () { it('calls parse and stringify for STRING', function () {
var Type = new Sequelize.STRING(); const Type = new Sequelize.STRING();
return testSuccess(Type, 'foobar'); return testSuccess(Type, 'foobar');
}); });
it('calls parse and stringify for TEXT', function () { it('calls parse and stringify for TEXT', function () {
var Type = new Sequelize.TEXT(); const Type = new Sequelize.TEXT();
if (dialect === 'mssql') { if (dialect === 'mssql') {
// Text uses nvarchar, same type as string // Text uses nvarchar, same type as string
...@@ -195,25 +202,25 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -195,25 +202,25 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
it('calls parse and stringify for BOOLEAN', function () { it('calls parse and stringify for BOOLEAN', function () {
var Type = new Sequelize.BOOLEAN(); const Type = new Sequelize.BOOLEAN();
return testSuccess(Type, true); return testSuccess(Type, true);
}); });
it('calls parse and stringify for INTEGER', function () { it('calls parse and stringify for INTEGER', function () {
var Type = new Sequelize.INTEGER(); const Type = new Sequelize.INTEGER();
return testSuccess(Type, 1); return testSuccess(Type, 1);
}); });
it('calls parse and stringify for DECIMAL', function () { it('calls parse and stringify for DECIMAL', function () {
var Type = new Sequelize.DECIMAL(); const Type = new Sequelize.DECIMAL();
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5);
}); });
it('calls parse and stringify for BIGINT', function () { it('calls parse and stringify for BIGINT', function () {
var Type = new Sequelize.BIGINT(); const Type = new Sequelize.BIGINT();
if (dialect === 'mssql') { if (dialect === 'mssql') {
// Same type as integer // Same type as integer
...@@ -224,13 +231,13 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -224,13 +231,13 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
it('calls parse and stringify for DOUBLE', function () { it('calls parse and stringify for DOUBLE', function () {
var Type = new Sequelize.DOUBLE(); const Type = new Sequelize.DOUBLE();
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5);
}); });
it('calls parse and stringify for FLOAT', function () { it('calls parse and stringify for FLOAT', function () {
var Type = new Sequelize.FLOAT(); const Type = new Sequelize.FLOAT();
if (dialect === 'postgres') { if (dialect === 'postgres') {
// Postgres doesn't have float, maps to either decimal or double // Postgres doesn't have float, maps to either decimal or double
...@@ -241,25 +248,15 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -241,25 +248,15 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
it('calls parse and stringify for REAL', function () { it('calls parse and stringify for REAL', function () {
var Type = new Sequelize.REAL(); const Type = new Sequelize.REAL();
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5);
}); });
it('calls parse and stringify for GEOMETRY', function () {
var Type = new Sequelize.GEOMETRY();
if (['postgres', 'mysql'].indexOf(dialect) !== -1) {
return testSuccess(Type, { type: "Point", coordinates: [125.6, 10.1] });
} else {
// Not implemented yet
testFailure(Type);
}
});
it('calls parse and stringify for UUID', function () { it('calls parse and stringify for UUID', function () {
var Type = new Sequelize.UUID(); const Type = new Sequelize.UUID();
// there is no dialect.supports.UUID yet
if (['postgres', 'sqlite'].indexOf(dialect) !== -1) { if (['postgres', 'sqlite'].indexOf(dialect) !== -1) {
return testSuccess(Type, uuid.v4()); return testSuccess(Type, uuid.v4());
} else { } else {
...@@ -269,82 +266,90 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -269,82 +266,90 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
it('calls parse and stringify for ENUM', function () { it('calls parse and stringify for ENUM', function () {
var 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.. // No dialects actually allow us to identify that we get an enum back..
testFailure(Type); testFailure(Type);
}); });
it('should parse an empty GEOMETRY field', function () { if (current.dialect.supports.GEOMETRY) {
var Type = new Sequelize.GEOMETRY(); it('calls parse and stringify for GEOMETRY', function () {
const Type = new Sequelize.GEOMETRY();
// MySQL 5.7 or above doesn't support POINT EMPTY return testSuccess(Type, { type: "Point", coordinates: [125.6, 10.1] });
if (dialect === 'mysql' && semver.gte(current.options.databaseVersion, '5.7.0')) { });
return;
}
return new Sequelize.Promise((resolve, reject) => { it('should parse an empty GEOMETRY field', function () {
if (/^postgres/.test(dialect)) { const Type = new Sequelize.GEOMETRY();
current.query(`SELECT PostGIS_Lib_Version();`)
.then((result) => { // MySQL 5.7 or above doesn't support POINT EMPTY
if (result[0][0] && semver.lte(result[0][0].postgis_lib_version, '2.1.7')) { if (dialect === 'mysql' && semver.gte(current.options.databaseVersion, '5.7.0')) {
resolve(true); return;
} else {
resolve();
}
}).catch(reject);
} else {
resolve(true);
} }
}).then((runTests) => {
if (current.dialect.supports.GEOMETRY && runTests) {
current.refreshTypes();
var User = current.define('user', { field: Type }, { timestamps: false }); return new Sequelize.Promise((resolve, reject) => {
var point = { type: "Point", coordinates: [] }; if (/^postgres/.test(dialect)) {
current.query(`SELECT PostGIS_Lib_Version();`)
.then((result) => {
if (result[0][0] && semver.lte(result[0][0].postgis_lib_version, '2.1.7')) {
resolve(true);
} else {
resolve();
}
}).catch(reject);
} else {
resolve(true);
}
}).then((runTests) => {
if (current.dialect.supports.GEOMETRY && runTests) {
current.refreshTypes();
const User = current.define('user', { field: Type }, { timestamps: false });
const point = { type: "Point", coordinates: [] };
return current.sync({ force: true }).then(function () { return current.sync({ force: true }).then(() => {
return User.create({ return User.create({
//insert a null GEOMETRY type //insert a null GEOMETRY type
field: point field: point
});
}).then(() => {
//This case throw unhandled exception
return User.findAll();
}).then(users =>{
if (dialect === 'mysql') {
// MySQL will return NULL, becuase they lack EMPTY geometry data support.
expect(users[0].field).to.be.eql(null);
} else if (dialect === 'postgres' || dialect === 'postgres-native') {
//Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996
expect(users[0].field).to.be.deep.eql({ type: "Point", coordinates: [0,0] });
} else {
expect(users[0].field).to.be.deep.eql(point);
}
}); });
}).then(function () { }
//This case throw unhandled exception });
return User.findAll();
}).then(function(users){
if (dialect === 'mysql') {
// MySQL will return NULL, becuase they lack EMPTY geometry data support.
expect(users[0].field).to.be.eql(null);
} else if (dialect === 'postgres' || dialect === 'postgres-native') {
//Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996
expect(users[0].field).to.be.deep.eql({ type: "Point", coordinates: [0,0] });
} else {
expect(users[0].field).to.be.deep.eql(point);
}
});
}
}); });
}); }
if (dialect === 'postgres' || dialect === 'sqlite') { if (dialect === 'postgres' || dialect === 'sqlite') {
// postgres actively supports IEEE floating point literals, and sqlite doesn't care what we throw at it // postgres actively supports IEEE floating point literals, and sqlite doesn't care what we throw at it
it('should store and parse IEEE floating point literals (NaN and Infinity)', function () { it('should store and parse IEEE floating point literals (NaN and Infinity)', function () {
var Model = this.sequelize.define('model', { const Model = this.sequelize.define('model', {
float: Sequelize.FLOAT, float: Sequelize.FLOAT,
double: Sequelize.DOUBLE, double: Sequelize.DOUBLE,
real: Sequelize.REAL real: Sequelize.REAL
}); });
return Model.sync({ force: true }).then(function () { return Model.sync({ force: true }).then(() => {
return Model.create({ return Model.create({
id: 1, id: 1,
float: NaN, float: NaN,
double: Infinity, double: Infinity,
real: -Infinity real: -Infinity
}); });
}).then(function () { }).then(() => {
return Model.find({ where:{ id: 1 } }); return Model.find({ where:{ id: 1 } });
}).then(function (user) { }).then(user => {
expect(user.get('float')).to.be.NaN; expect(user.get('float')).to.be.NaN;
expect(user.get('double')).to.eq(Infinity); expect(user.get('double')).to.eq(Infinity);
expect(user.get('real')).to.eq(-Infinity); expect(user.get('real')).to.eq(-Infinity);
...@@ -354,7 +359,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -354,7 +359,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
if (dialect === 'postgres') { if (dialect === 'postgres') {
it('should parse DECIMAL as string', function () { it('should parse DECIMAL as string', function () {
var Model = this.sequelize.define('model', { const Model = this.sequelize.define('model', {
decimal: Sequelize.DECIMAL, decimal: Sequelize.DECIMAL,
decimalPre: Sequelize.DECIMAL(10, 4), decimalPre: Sequelize.DECIMAL(10, 4),
decimalWithParser: Sequelize.DECIMAL(32, 15), decimalWithParser: Sequelize.DECIMAL(32, 15),
...@@ -362,7 +367,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -362,7 +367,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
decimalWithFloatParser: Sequelize.DECIMAL(10, 8) decimalWithFloatParser: Sequelize.DECIMAL(10, 8)
}); });
var sampleData = { const sampleData = {
id: 1, id: 1,
decimal: 12345678.12345678, decimal: 12345678.12345678,
decimalPre: 123456.1234, decimalPre: 123456.1234,
...@@ -371,11 +376,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -371,11 +376,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
decimalWithFloatParser: 0.12345678 decimalWithFloatParser: 0.12345678
}; };
return Model.sync({ force: true }).then(function () { return Model.sync({ force: true }).then(() => {
return Model.create(sampleData); return Model.create(sampleData);
}).then(function () { }).then(() => {
return Model.find({id: 1}); return Model.find({id: 1});
}).then(function (user) { }).then(user => {
expect(user.get('decimal')).to.be.eql('12345678.12345678'); expect(user.get('decimal')).to.be.eql('12345678.12345678');
expect(user.get('decimalPre')).to.be.eql('123456.1234'); expect(user.get('decimalPre')).to.be.eql('123456.1234');
expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567'); expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567');
...@@ -385,7 +390,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -385,7 +390,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
}); });
it('should return Int4 range properly #5747', function() { it('should return Int4 range properly #5747', function() {
var Model = this.sequelize.define('M', { const Model = this.sequelize.define('M', {
interval: { interval: {
type: Sequelize.RANGE(Sequelize.INTEGER), type: Sequelize.RANGE(Sequelize.INTEGER),
allowNull: false, allowNull: false,
...@@ -405,20 +410,20 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -405,20 +410,20 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
if (dialect === 'mysql') { if (dialect === 'mysql') {
it('should parse BIGINT as string', function () { it('should parse BIGINT as string', function () {
var Model = this.sequelize.define('model', { const Model = this.sequelize.define('model', {
jewelPurity: Sequelize.BIGINT jewelPurity: Sequelize.BIGINT
}); });
var sampleData = { const sampleData = {
id: 1, id: 1,
jewelPurity: '9223372036854775807', jewelPurity: '9223372036854775807',
}; };
return Model.sync({ force: true }).then(function () { return Model.sync({ force: true }).then(() => {
return Model.create(sampleData); return Model.create(sampleData);
}).then(function () { }).then(() => {
return Model.findById(1); return Model.findById(1);
}).then(function (user) { }).then(user => {
expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity); expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity);
expect(user.get('jewelPurity')).to.be.string; expect(user.get('jewelPurity')).to.be.string;
}); });
...@@ -426,14 +431,14 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -426,14 +431,14 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
} }
it('should allow spaces in ENUM', function () { it('should allow spaces in ENUM', function () {
var Model = this.sequelize.define('user', { const Model = this.sequelize.define('user', {
name: Sequelize.STRING, name: Sequelize.STRING,
type: Sequelize.ENUM(['action', 'mecha', 'canon', 'class s']) type: Sequelize.ENUM(['action', 'mecha', 'canon', 'class s'])
}); });
return Model.sync({ force: true}).then(function () { return Model.sync({ force: true }).then(() => {
return Model.create({ name: 'sakura', type: 'class s' }); return Model.create({ name: 'sakura', type: 'class s' });
}).then(function (record) { }).then(record => {
expect(record.type).to.be.eql('class s'); expect(record.type).to.be.eql('class s');
}); });
}); });
......
'use strict'; 'use strict';
/* jshint -W030 */ /* jshint -W030 */
/* jshint -W079 */
/* jshint -W110 */ /* jshint -W110 */
var chai = require('chai') const chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../../support') , Support = require(__dirname + '/../../support')
, Sequelize = Support.Sequelize , Sequelize = Support.Sequelize
, Promise = Sequelize.Promise , Promise = Sequelize.Promise
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, DataTypes = require(__dirname + '/../../../../lib/data-types') , DataTypes = require(__dirname + '/../../../../lib/data-types')
, sequelize = require(__dirname + '/../../../../lib/sequelize'); , sequelize = require(__dirname + '/../../../../lib/sequelize');
if (dialect.match(/^postgres/)) { if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] DAO', function() { describe('[POSTGRES Specific] DAO', function () {
beforeEach(function() { beforeEach(function () {
this.sequelize.options.quoteIdentifiers = true; this.sequelize.options.quoteIdentifiers = true;
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
...@@ -40,30 +41,30 @@ if (dialect.match(/^postgres/)) { ...@@ -40,30 +41,30 @@ if (dialect.match(/^postgres/)) {
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
afterEach(function() { afterEach(function () {
this.sequelize.options.quoteIdentifiers = true; this.sequelize.options.quoteIdentifiers = true;
}); });
it('should be able to search within an array', function() { it('should be able to search within an array', function () {
return this.User.findAll({ return this.User.findAll({
where: { where: {
email: ['hello', 'world'] email: ['hello', 'world']
}, },
attributes: ['id','username','email','settings','document','phones','emergency_contact','friends'], attributes: ['id', 'username', 'email', 'settings', 'document', 'phones', 'emergency_contact', 'friends'],
logging: function (sql) { logging: function (sql) {
expect(sql).to.equal('Executing (default): SELECT "id", "username", "email", "settings", "document", "phones", "emergency_contact", "friends" FROM "Users" AS "User" WHERE "User"."email" = ARRAY[\'hello\',\'world\']::TEXT[];'); expect(sql).to.equal('Executing (default): SELECT "id", "username", "email", "settings", "document", "phones", "emergency_contact", "friends" FROM "Users" AS "User" WHERE "User"."email" = ARRAY[\'hello\',\'world\']::TEXT[];');
} }
}); });
}); });
it('should be able to update a field with type ARRAY(JSON)', function(){ it('should be able to update a field with type ARRAY(JSON)', function () {
return this.User.create({ return this.User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
friends: [{ friends: [{
name: 'John Smith' name: 'John Smith'
}] }]
}).then(function(userInstance){ }).then(userInstance => {
expect(userInstance.friends).to.have.length(1); expect(userInstance.friends).to.have.length(1);
expect(userInstance.friends[0].name).to.equal('John Smith'); expect(userInstance.friends[0].name).to.equal('John Smith');
...@@ -72,21 +73,19 @@ if (dialect.match(/^postgres/)) { ...@@ -72,21 +73,19 @@ if (dialect.match(/^postgres/)) {
name: 'John Smythe' name: 'John Smythe'
}] }]
}); });
}) }).get('friends')
.get('friends') .tap(friends => {
.tap(function(friends){ expect(friends).to.have.length(1);
expect(friends).to.have.length(1); expect(friends[0].name).to.equal('John Smythe');
expect(friends[0].name).to.equal('John Smythe'); });
});
}); });
it('should be able to find a record while searching in an array', function() { it('should be able to find a record while searching in an array', function () {
var self = this;
return this.User.bulkCreate([ return this.User.bulkCreate([
{username: 'bob', email: ['myemail@email.com']}, { username: 'bob', email: ['myemail@email.com'] },
{username: 'tony', email: ['wrongemail@email.com']} { username: 'tony', email: ['wrongemail@email.com'] }
]).then(function() { ]).then(() => {
return self.User.findAll({where: {email: ['myemail@email.com']}}).then(function(user) { return this.User.findAll({ where: { email: ['myemail@email.com'] } }).then(user => {
expect(user).to.be.instanceof(Array); expect(user).to.be.instanceof(Array);
expect(user).to.have.length(1); expect(user).to.have.length(1);
expect(user[0].username).to.equal('bob'); expect(user[0].username).to.equal('bob');
...@@ -94,245 +93,119 @@ if (dialect.match(/^postgres/)) { ...@@ -94,245 +93,119 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
describe('json', function() { describe('json', function () {
it('should tell me that a column is json', function() { it('should be able to retrieve a row with ->> operator', function () {
return this.sequelize.queryInterface.describeTable('Users')
.then(function(table) {
expect(table.emergency_contact.type).to.equal('JSON');
});
});
it('should stringify json with insert', function() {
return this.User.create({
username: 'bob',
emergency_contact: { name: 'joe', phones: [1337, 42] }
}, {
fields: ['id', 'username', 'document', 'emergency_contact'],
logging: function(sql) {
var expected = '\'{"name":"joe","phones":[1337,42]}\'';
expect(sql.indexOf(expected)).not.to.equal(-1);
}
});
});
it('should insert json using a custom field name', function() {
var self = this;
this.UserFields = this.sequelize.define('UserFields', {
emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' }
});
return this.UserFields.sync({ force: true }).then(function() {
return self.UserFields.create({
emergencyContact: { name: 'joe', phones: [1337, 42] }
}).then(function(user) {
expect(user.emergencyContact.name).to.equal('joe');
});
});
});
it('should update json using a custom field name', function() {
var self = this;
this.UserFields = this.sequelize.define('UserFields', {
emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' }
});
return this.UserFields.sync({ force: true }).then(function() {
return self.UserFields.create({
emergencyContact: { name: 'joe', phones: [1337, 42] }
}).then(function(user) {
user.emergencyContact = { name: 'larry' };
return user.save();
}).then(function(user) {
expect(user.emergencyContact.name).to.equal('larry');
});
});
});
it('should be able retrieve json value as object', function() {
var self = this;
var emergencyContact = { name: 'kate', phone: 1337 };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function(user) {
expect(user.emergency_contact).to.eql(emergencyContact); // .eql does deep value comparison instead of
// strict equal comparison
return self.User.find({ where: { username: 'swen' }, attributes: ['emergency_contact'] });
})
.then(function(user) {
expect(user.emergency_contact).to.eql(emergencyContact);
});
});
it('should be able to retrieve element of array by index', function() {
var self = this;
var emergencyContact = { name: 'kate', phones: [1337, 42] };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function(user) {
expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.phones.1'), 'firstEmergencyNumber']] });
})
.then(function(user) {
expect(parseInt(user.getDataValue('firstEmergencyNumber'))).to.equal(42);
});
});
it('should be able to retrieve root level value of an object by key', function() {
var self = this;
var emergencyContact = { kate: 1337 };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function(user) {
expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate'), 'katesNumber']] });
})
.then(function(user) {
expect(parseInt(user.getDataValue('katesNumber'))).to.equal(1337);
});
});
it('should be able to retrieve nested value of an object by path', function() {
var self = this;
var emergencyContact = { kate: { email: 'kate@kate.com', phones: [1337, 42] } };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(function(user) {
expect(user.emergency_contact).to.eql(emergencyContact);
return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate.email'), 'katesEmail']] });
})
.then(function(user) {
expect(user.getDataValue('katesEmail')).to.equal('kate@kate.com');
})
.then(function() {
return self.User.find({ where: { username: 'swen' }, attributes: [[sequelize.json('emergency_contact.kate.phones.1'), 'katesFirstPhone']] });
})
.then(function(user) {
expect(parseInt(user.getDataValue('katesFirstPhone'))).to.equal(42);
});
});
it('should be able to retrieve a row based on the values of the json document', function() {
var self = this;
return this.sequelize.Promise.all([ return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })])
.then(function() { .then(() => {
return self.User.find({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] }); return this.User.find({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] });
}) })
.then(function(user) { .then(user => {
expect(user.emergency_contact.name).to.equal('kate'); expect(user.emergency_contact.name).to.equal('kate');
}); });
}); });
it('should be able to query using the nested query language', function() { it('should be able to query using the nested query language', function () {
var self = this;
return this.sequelize.Promise.all([ return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })])
.then(function() { .then(() => {
return self.User.find({ return this.User.find({
where: sequelize.json({ emergency_contact: { name: 'kate' } }) where: sequelize.json({ emergency_contact: { name: 'kate' } })
}); });
}) })
.then(function(user) { .then(user => {
expect(user.emergency_contact.name).to.equal('kate'); expect(user.emergency_contact.name).to.equal('kate');
}); });
}); });
it('should be able to query using dot syntax', function() { it('should be able to query using dot syntax', function () {
var self = this;
return this.sequelize.Promise.all([ return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })])
.then(function() { .then(() => {
return self.User.find({ where: sequelize.json('emergency_contact.name', 'joe') }); return this.User.find({ where: sequelize.json('emergency_contact.name', 'joe') });
}) })
.then(function(user) { .then(user => {
expect(user.emergency_contact.name).to.equal('joe'); expect(user.emergency_contact.name).to.equal('joe');
}); });
}); });
it('should be able to query using dot syntax with uppercase name', function() { it('should be able to query using dot syntax with uppercase name', function () {
var self = this;
return this.sequelize.Promise.all([ return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]) this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })])
.then(function() { .then(() => {
return self.User.find({ return this.User.find({
attributes: [[sequelize.json('emergencyContact.name'), 'contactName']], attributes: [[sequelize.json('emergencyContact.name'), 'contactName']],
where: sequelize.json('emergencyContact.name', 'joe') where: sequelize.json('emergencyContact.name', 'joe')
}); });
}) })
.then(function(user) { .then(user => {
expect(user.get("contactName")).to.equal('joe'); expect(user.get("contactName")).to.equal('joe');
}); });
}); });
it('should be able to store values that require JSON escaping', function() { it('should be able to store values that require JSON escaping', function () {
var self = this; const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values";
var text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values";
return this.User.create({ username: 'swen', emergency_contact: { value: text } }) return this.User.create({ username: 'swen', emergency_contact: { value: text } })
.then(function(user) { .then(user => {
expect(user.isNewRecord).to.equal(false); expect(user.isNewRecord).to.equal(false);
}) })
.then(function() { .then(() => {
return self.User.find({ where: { username: 'swen' } }); return this.User.find({ where: { username: 'swen' } });
}) })
.then(function() { .then(() => {
return self.User.find({ where: sequelize.json('emergency_contact.value', text) }); return this.User.find({ where: sequelize.json('emergency_contact.value', text) });
}) })
.then(function(user) { .then(user => {
expect(user.username).to.equal('swen'); expect(user.username).to.equal('swen');
}); });
}); });
it('should be able to findOrCreate with values that require JSON escaping', function() { it('should be able to findOrCreate with values that require JSON escaping', function () {
var self = this; const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values";
var text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values";
return this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } }) return this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } })
.then(function(user) { .then(user => {
expect(!user.isNewRecord).to.equal(true); expect(!user.isNewRecord).to.equal(true);
}) })
.then(function() { .then(() => {
return self.User.find({ where: { username: 'swen' } }); return this.User.find({ where: { username: 'swen' } });
}) })
.then(function() { .then(() => {
return self.User.find({ where: sequelize.json('emergency_contact.value', text) }); return this.User.find({ where: sequelize.json('emergency_contact.value', text) });
}) })
.then(function(user) { .then(user => {
expect(user.username).to.equal('swen'); expect(user.username).to.equal('swen');
}); });
}); });
}); });
describe('hstore', function() { describe('hstore', function () {
it('should tell me that a column is hstore and not USER-DEFINED', function() { it('should tell me that a column is hstore and not USER-DEFINED', function () {
return this.sequelize.queryInterface.describeTable('Users').then(function(table) { return this.sequelize.queryInterface.describeTable('Users').then(table => {
expect(table.settings.type).to.equal('HSTORE'); expect(table.settings.type).to.equal('HSTORE');
expect(table.document.type).to.equal('HSTORE'); expect(table.document.type).to.equal('HSTORE');
}); });
}); });
it('should stringify hstore with insert', function() { it('should stringify hstore with insert', function () {
return this.User.create({ return this.User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
settings: {mailing: false, push: 'facebook', frequency: 3} settings: { mailing: false, push: 'facebook', frequency: 3 }
}, { }, {
logging: function (sql) { logging(sql) {
var expected = '\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\''; const expected = '\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\'';
expect(sql.indexOf(expected)).not.to.equal(-1); expect(sql.indexOf(expected)).not.to.equal(-1);
} }
}); });
}); });
it('should not rename hstore fields', function() { it('should not rename hstore fields', function () {
const Equipment = this.sequelize.define('Equipment', { const Equipment = this.sequelize.define('Equipment', {
grapplingHook: { grapplingHook: {
type: DataTypes.STRING, type: DataTypes.STRING,
...@@ -357,7 +230,7 @@ if (dialect.match(/^postgres/)) { ...@@ -357,7 +230,7 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should not rename json fields', function() { it('should not rename json fields', function () {
const Equipment = this.sequelize.define('Equipment', { const Equipment = this.sequelize.define('Equipment', {
grapplingHook: { grapplingHook: {
type: DataTypes.STRING, type: DataTypes.STRING,
...@@ -376,7 +249,7 @@ if (dialect.match(/^postgres/)) { ...@@ -376,7 +249,7 @@ if (dialect.match(/^postgres/)) {
} }
}, },
logging(sql) { logging(sql) {
expect(sql).to.equal('Executing (default): SELECT "id", "grappling_hook" AS "grapplingHook", "utilityBelt", "createdAt", "updatedAt" FROM "Equipment" AS "Equipment" WHERE ("Equipment"."utilityBelt"#>>\'{grapplingHook}\')::boolean = true;'); expect(sql).to.equal('Executing (default): SELECT "id", "grappling_hook" AS "grapplingHook", "utilityBelt", "createdAt", "updatedAt" FROM "Equipment" AS "Equipment" WHERE CAST(("Equipment"."utilityBelt"#>>\'{grapplingHook}\') AS BOOLEAN) = true;');
} }
}); });
}); });
...@@ -384,9 +257,9 @@ if (dialect.match(/^postgres/)) { ...@@ -384,9 +257,9 @@ if (dialect.match(/^postgres/)) {
}); });
describe('range', function() { describe('range', function () {
it('should tell me that a column is range and not USER-DEFINED', function() { it('should tell me that a column is range and not USER-DEFINED', function () {
return this.sequelize.queryInterface.describeTable('Users').then(function(table) { return this.sequelize.queryInterface.describeTable('Users').then(table => {
expect(table.course_period.type).to.equal('TSTZRANGE'); expect(table.course_period.type).to.equal('TSTZRANGE');
expect(table.available_amount.type).to.equal('INT4RANGE'); expect(table.available_amount.type).to.equal('INT4RANGE');
}); });
...@@ -394,29 +267,29 @@ if (dialect.match(/^postgres/)) { ...@@ -394,29 +267,29 @@ if (dialect.match(/^postgres/)) {
}); });
describe('enums', function() { describe('enums', function () {
it('should be able to ignore enum types that already exist', function() { it('should be able to ignore enum types that already exist', function () {
var User = this.sequelize.define('UserEnums', { const User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh') mood: DataTypes.ENUM('happy', 'sad', 'meh')
}); });
return User.sync({ force: true }).then(function() { return User.sync({ force: true }).then(() => {
return User.sync(); return User.sync();
}); });
}); });
it('should be able to create/drop enums multiple times', function() { it('should be able to create/drop enums multiple times', function () {
var User = this.sequelize.define('UserEnums', { const User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh') mood: DataTypes.ENUM('happy', 'sad', 'meh')
}); });
return User.sync({ force: true }).then(function() { return User.sync({ force: true }).then(() => {
return User.sync({ force: true }); return User.sync({ force: true });
}); });
}); });
it('should be able to create/drop multiple enums multiple times', function() { it('should be able to create/drop multiple enums multiple times', function () {
var DummyModel = this.sequelize.define('Dummy-pg', { const DummyModel = this.sequelize.define('Dummy-pg', {
username: DataTypes.STRING, username: DataTypes.STRING,
theEnumOne: { theEnumOne: {
type: DataTypes.ENUM, type: DataTypes.ENUM,
...@@ -436,38 +309,38 @@ if (dialect.match(/^postgres/)) { ...@@ -436,38 +309,38 @@ if (dialect.match(/^postgres/)) {
} }
}); });
return DummyModel.sync({ force: true }).then(function() { return DummyModel.sync({ force: true }).then(() => {
// now sync one more time: // now sync one more time:
return DummyModel.sync({force: true}).then(function() { return DummyModel.sync({ force: true }).then(() => {
// sync without dropping // sync without dropping
return DummyModel.sync(); 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 () {
var User = this.sequelize.define('UserEnums', { let User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh') mood: DataTypes.ENUM('happy', 'sad', 'meh')
}); });
return User.sync({ force: true }).bind(this).then(function() { return User.sync({ force: true }).then(() => {
User = this.sequelize.define('UserEnums', { User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful') mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful')
}); });
return User.sync(); return User.sync();
}).then(function() { }).then(() => {
return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); return this.sequelize.getQueryInterface().pgListEnums(User.getTableName());
}).then(function (enums) { }).then(enums => {
expect(enums).to.have.length(1); expect(enums).to.have.length(1);
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('integers', function() { describe('integers', function () {
describe('integer', function() { describe('integer', function () {
beforeEach(function() { beforeEach(function () {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
aNumber: DataTypes.INTEGER aNumber: DataTypes.INTEGER
}); });
...@@ -475,31 +348,31 @@ if (dialect.match(/^postgres/)) { ...@@ -475,31 +348,31 @@ if (dialect.match(/^postgres/)) {
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
it('positive', function() { it('positive', function () {
var User = this.User; const User = this.User;
return User.create({aNumber: 2147483647}).then(function(user) { return User.create({ aNumber: 2147483647 }).then(user => {
expect(user.aNumber).to.equal(2147483647); expect(user.aNumber).to.equal(2147483647);
return User.find({where: {aNumber: 2147483647}}).then(function(_user) { return User.find({ where: { aNumber: 2147483647 } }).then(_user => {
expect(_user.aNumber).to.equal(2147483647); expect(_user.aNumber).to.equal(2147483647);
}); });
}); });
}); });
it('negative', function() { it('negative', function () {
var User = this.User; const User = this.User;
return User.create({aNumber: -2147483647}).then(function(user) { return User.create({ aNumber: -2147483647 }).then(user => {
expect(user.aNumber).to.equal(-2147483647); expect(user.aNumber).to.equal(-2147483647);
return User.find({where: {aNumber: -2147483647}}).then(function(_user) { return User.find({ where: { aNumber: -2147483647 } }).then(_user => {
expect(_user.aNumber).to.equal(-2147483647); expect(_user.aNumber).to.equal(-2147483647);
}); });
}); });
}); });
}); });
describe('bigint', function() { describe('bigint', function () {
beforeEach(function() { beforeEach(function () {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
aNumber: DataTypes.BIGINT aNumber: DataTypes.BIGINT
}); });
...@@ -507,23 +380,23 @@ if (dialect.match(/^postgres/)) { ...@@ -507,23 +380,23 @@ if (dialect.match(/^postgres/)) {
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
it('positive', function() { it('positive', function () {
var User = this.User; const User = this.User;
return User.create({aNumber: '9223372036854775807'}).then(function(user) { return User.create({ aNumber: '9223372036854775807' }).then(user => {
expect(user.aNumber).to.equal('9223372036854775807'); expect(user.aNumber).to.equal('9223372036854775807');
return User.find({where: {aNumber: '9223372036854775807'}}).then(function(_user) { return User.find({ where: { aNumber: '9223372036854775807' } }).then(_user => {
expect(_user.aNumber).to.equal('9223372036854775807'); expect(_user.aNumber).to.equal('9223372036854775807');
}); });
}); });
}); });
it('negative', function() { it('negative', function () {
var User = this.User; const User = this.User;
return User.create({aNumber: '-9223372036854775807'}).then(function(user) { return User.create({ aNumber: '-9223372036854775807' }).then(user => {
expect(user.aNumber).to.equal('-9223372036854775807'); expect(user.aNumber).to.equal('-9223372036854775807');
return User.find({where: {aNumber: '-9223372036854775807'}}).then(function(_user) { return User.find({ where: { aNumber: '-9223372036854775807' } }).then(_user => {
expect(_user.aNumber).to.equal('-9223372036854775807'); expect(_user.aNumber).to.equal('-9223372036854775807');
}); });
}); });
...@@ -531,57 +404,57 @@ if (dialect.match(/^postgres/)) { ...@@ -531,57 +404,57 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
describe('timestamps', function() { describe('timestamps', function () {
beforeEach(function() { beforeEach(function () {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
dates: DataTypes.ARRAY(DataTypes.DATE) dates: DataTypes.ARRAY(DataTypes.DATE)
}); });
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
it('should use postgres "TIMESTAMP WITH TIME ZONE" instead of "DATETIME"', function() { it('should use postgres "TIMESTAMP WITH TIME ZONE" instead of "DATETIME"', function () {
return this.User.create({ return this.User.create({
dates: [] dates: []
}, { }, {
logging: function(sql) { logging: function (sql) {
expect(sql.indexOf('TIMESTAMP WITH TIME ZONE')).to.be.greaterThan(0); expect(sql.indexOf('TIMESTAMP WITH TIME ZONE')).to.be.greaterThan(0);
} }
}); });
}); });
}); });
describe('model', function() { describe('model', function () {
it('create handles array correctly', function() { it('create handles array correctly', function () {
return this.User return this.User
.create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }) .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] })
.then(function(oldUser) { .then(oldUser => {
expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']); expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']);
}); });
}); });
it('should save hstore correctly', function() { it('should save hstore correctly', function () {
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' }}).then(function(newUser) { return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' } }).then(newUser => {
// Check to see if the default value for an hstore field works // Check to see if the default value for an hstore field works
expect(newUser.document).to.deep.equal({ default: "'value'" }); expect(newUser.document).to.deep.equal({ default: "'value'" });
expect(newUser.settings).to.deep.equal({ created: '"value"' }); expect(newUser.settings).to.deep.equal({ created: '"value"' });
// Check to see if updating an hstore field works // Check to see if updating an hstore field works
return newUser.updateAttributes({settings: {should: 'update', to: 'this', first: 'place'}}).then(function(oldUser) { return newUser.updateAttributes({ settings: { should: 'update', to: 'this', first: 'place' } }).then(oldUser => {
// Postgres always returns keys in alphabetical order (ascending) // Postgres always returns keys in alphabetical order (ascending)
expect(oldUser.settings).to.deep.equal({first: 'place', should: 'update', to: 'this'}); expect(oldUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' });
}); });
}); });
}); });
it('should save hstore array correctly', function() { it('should save hstore array correctly', function () {
var User = this.User; const User = this.User;
return this.User.create({ return this.User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }, { number: '8675309', type: "Jenny's"}, {number: '5555554321', type: '"home\n"' }] phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }, { number: '8675309', type: "Jenny's" }, { number: '5555554321', type: '"home\n"' }]
}).then(function() { }).then(() => {
return User.findById(1).then(function(user) { return User.findById(1).then(user => {
expect(user.phones.length).to.equal(4); expect(user.phones.length).to.equal(4);
expect(user.phones[1].number).to.equal('987654321'); expect(user.phones[1].number).to.equal('987654321');
expect(user.phones[2].type).to.equal("Jenny's"); expect(user.phones[2].type).to.equal("Jenny's");
...@@ -590,128 +463,119 @@ if (dialect.match(/^postgres/)) { ...@@ -590,128 +463,119 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should bulkCreate with hstore property', function() { it('should bulkCreate with hstore property', function () {
var User = this.User; const User = this.User;
return this.User.bulkCreate([{ return this.User.bulkCreate([{
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
settings: {mailing: true, push: 'facebook', frequency: 3} settings: { mailing: true, push: 'facebook', frequency: 3 }
}]).then(function() { }]).then(() => {
return User.findById(1).then(function(user) { return User.findById(1).then(user => {
expect(user.settings.mailing).to.equal('true'); expect(user.settings.mailing).to.equal('true');
}); });
}); });
}); });
it('should update hstore correctly', function() { it('should update hstore correctly', function () {
var self = this; return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(newUser => {
// Check to see if the default value for an hstore field works
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }}).then(function(newUser) { expect(newUser.document).to.deep.equal({ default: "'value'" });
// Check to see if the default value for an hstore field works expect(newUser.settings).to.deep.equal({ test: '"value"' });
expect(newUser.document).to.deep.equal({ default: "'value'" });
expect(newUser.settings).to.deep.equal({ test: '"value"' });
// Check to see if updating an hstore field works // Check to see if updating an hstore field works
return self.User.update({ settings: { should: 'update', to: 'this', first: 'place' }}, { where: newUser.where() }).then(function() { return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: newUser.where() }).then(() => {
return newUser.reload().then(function() { return newUser.reload().then(() => {
// Postgres always returns keys in alphabetical order (ascending) // Postgres always returns keys in alphabetical order (ascending)
expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' });
});
}); });
}); });
});
}); });
it('should update hstore correctly and return the affected rows', function() { it('should update hstore correctly and return the affected rows', function () {
var self = this; return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(oldUser => {
// Update the user and check that the returned object's fields have been parsed by the hstore library
return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }}).then(function(oldUser) { return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: oldUser.where(), returning: true }).spread(function (count, users) {
// Update the user and check that the returned object's fields have been parsed by the hstore library expect(count).to.equal(1);
return self.User.update({ settings: { should: 'update', to: 'this', first: 'place' }}, { where: oldUser.where(), returning: true }).spread(function(count, users) { expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' });
expect(count).to.equal(1);
expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' });
});
}); });
});
}); });
it('should read hstore correctly', function() { it('should read hstore correctly', function () {
var self = this; const data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } };
var data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' }};
return this.User.create(data) return this.User.create(data)
.then(function() { .then(() => {
return self.User.find({ where: { username: 'user' }}); return this.User.find({ where: { username: 'user' } });
}) })
.then(function(user) { .then(user => {
// Check that the hstore fields are the same when retrieving the user // Check that the hstore fields are the same when retrieving the user
expect(user.settings).to.deep.equal(data.settings); expect(user.settings).to.deep.equal(data.settings);
}); });
}); });
it('should read an hstore array correctly', function() { it('should read an hstore array correctly', function () {
var self = this; const data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] };
var data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] };
return this.User.create(data) return this.User.create(data)
.then(function() { .then(() => {
// Check that the hstore fields are the same when retrieving the user // Check that the hstore fields are the same when retrieving the user
return self.User.find({ where: { username: 'user' }}); return this.User.find({ where: { username: 'user' } });
}).then(function(user) { }).then(user => {
expect(user.phones).to.deep.equal(data.phones); expect(user.phones).to.deep.equal(data.phones);
}); });
}); });
it('should read hstore correctly from multiple rows', function() { it('should read hstore correctly from multiple rows', function () {
var self = this; return this.User
.create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' } })
return self.User .then(() => {
.create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' }}) return this.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' } });
.then(function() {
return self.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' }});
}) })
.then(function() { .then(() => {
// Check that the hstore fields are the same when retrieving the user // Check that the hstore fields are the same when retrieving the user
return self.User.findAll({ order: ['username'] }); return this.User.findAll({ order: ['username'] });
}) })
.then(function(users) { .then(users => {
expect(users[0].settings).to.deep.equal({ test: '"value"' }); expect(users[0].settings).to.deep.equal({ test: '"value"' });
expect(users[1].settings).to.deep.equal({ another: '"example"' }); expect(users[1].settings).to.deep.equal({ another: '"example"' });
}); });
}); });
it('should read hstore correctly from included models as well', function() { it('should read hstore correctly from included models as well', function () {
var self = this, const HstoreSubmodel = this.sequelize.define('hstoreSubmodel', {
HstoreSubmodel = self.sequelize.define('hstoreSubmodel', { someValue: DataTypes.HSTORE
someValue: DataTypes.HSTORE });
}), const submodelValue = { testing: '"hstore"' };
submodelValue = { testing: '"hstore"' };
self.User.hasMany(HstoreSubmodel); this.User.hasMany(HstoreSubmodel);
return self.sequelize return this.sequelize
.sync({ force: true }) .sync({ force: true })
.then(function() { .then(() => {
return self.User.create({ username: 'user1' }) return this.User.create({ username: 'user1' })
.then(function (user) { .then(user => {
return HstoreSubmodel.create({ someValue: submodelValue}) return HstoreSubmodel.create({ someValue: submodelValue })
.then(function (submodel) { .then(submodel => {
return user.setHstoreSubmodels([submodel]); return user.setHstoreSubmodels([submodel]);
}); });
}); });
}) })
.then(function() { .then(() => {
return self.User.find({ where: { username: 'user1' }, include: [HstoreSubmodel]}); return this.User.find({ where: { username: 'user1' }, include: [HstoreSubmodel] });
}) })
.then(function(user) { .then(user => {
expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok; expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok;
expect(user.hstoreSubmodels.length).to.equal(1); expect(user.hstoreSubmodels.length).to.equal(1);
expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue); expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue);
}); });
}); });
it('should save range correctly', function() { it('should save range correctly', function () {
var period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period}).then(function(newUser) { return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => {
// Check to see if the default value for a range field works // Check to see if the default value for a range field works
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
...@@ -725,7 +589,7 @@ if (dialect.match(/^postgres/)) { ...@@ -725,7 +589,7 @@ if (dialect.match(/^postgres/)) {
expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive
// Check to see if updating a range field works // Check to see if updating a range field works
return newUser.updateAttributes({acceptable_marks: [0.8, 0.9]}).then(function() { return newUser.updateAttributes({ acceptable_marks: [0.8, 0.9] }).then(() => {
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
expect(newUser.acceptable_marks[0]).to.equal(0.8); // lower bound expect(newUser.acceptable_marks[0]).to.equal(0.8); // lower bound
expect(newUser.acceptable_marks[1]).to.equal(0.9); // upper bound expect(newUser.acceptable_marks[1]).to.equal(0.9); // upper bound
...@@ -733,19 +597,19 @@ if (dialect.match(/^postgres/)) { ...@@ -733,19 +597,19 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should save range array correctly', function() { it('should save range array correctly', function () {
var User = this.User, const User = this.User;
holidays = [ const holidays = [
[new Date(2015, 3, 1), new Date(2015, 3, 15)], [new Date(2015, 3, 1), new Date(2015, 3, 15)],
[new Date(2015, 8, 1), new Date(2015, 9, 15)] [new Date(2015, 8, 1), new Date(2015, 9, 15)]
]; ];
return User.create({ return User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
holidays: holidays holidays: holidays
}).then(function() { }).then(() => {
return User.findById(1).then(function(user) { return User.findById(1).then(user => {
expect(user.holidays.length).to.equal(2); expect(user.holidays.length).to.equal(2);
expect(user.holidays[0].length).to.equal(2); expect(user.holidays[0].length).to.equal(2);
expect(user.holidays[0][0] instanceof Date).to.be.ok; expect(user.holidays[0][0] instanceof Date).to.be.ok;
...@@ -761,16 +625,16 @@ if (dialect.match(/^postgres/)) { ...@@ -761,16 +625,16 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should bulkCreate with range property', function() { it('should bulkCreate with range property', function () {
var User = this.User, const User = this.User;
period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return User.bulkCreate([{ return User.bulkCreate([{
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
course_period: period course_period: period
}]).then(function() { }]).then(() => {
return User.findById(1).then(function(user) { return User.findById(1).then(user => {
expect(user.course_period[0] instanceof Date).to.be.ok; expect(user.course_period[0] instanceof Date).to.be.ok;
expect(user.course_period[1] instanceof Date).to.be.ok; expect(user.course_period[1] instanceof Date).to.be.ok;
expect(user.course_period[0]).to.equalTime(period[0]); // lower bound expect(user.course_period[0]).to.equalTime(period[0]); // lower bound
...@@ -780,11 +644,11 @@ if (dialect.match(/^postgres/)) { ...@@ -780,11 +644,11 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should update range correctly', function() { it('should update range correctly', function () {
var User = this.User const User = this.User;
, period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)];
return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(function(newUser) { return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => {
// Check to see if the default value for a range field works // Check to see if the default value for a range field works
expect(newUser.acceptable_marks.length).to.equal(2); expect(newUser.acceptable_marks.length).to.equal(2);
expect(newUser.acceptable_marks[0]).to.equal('0.65'); // lower bound expect(newUser.acceptable_marks[0]).to.equal('0.65'); // lower bound
...@@ -796,15 +660,15 @@ if (dialect.match(/^postgres/)) { ...@@ -796,15 +660,15 @@ if (dialect.match(/^postgres/)) {
expect(newUser.course_period[1]).to.equalTime(period[1]); // upper bound expect(newUser.course_period[1]).to.equalTime(period[1]); // upper bound
expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive
period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; const period2 = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
// Check to see if updating a range field works // Check to see if updating a range field works
return User.update({course_period: period}, {where: newUser.where()}).then(function() { return User.update({ course_period: period2 }, { where: newUser.where() }).then(() => {
return newUser.reload().then(function() { return newUser.reload().then(() => {
expect(newUser.course_period[0] instanceof Date).to.be.ok; expect(newUser.course_period[0] instanceof Date).to.be.ok;
expect(newUser.course_period[1] instanceof Date).to.be.ok; expect(newUser.course_period[1] instanceof Date).to.be.ok;
expect(newUser.course_period[0]).to.equalTime(period[0]); // lower bound expect(newUser.course_period[0]).to.equalTime(period2[0]); // lower bound
expect(newUser.course_period[1]).to.equalTime(period[1]); // upper bound expect(newUser.course_period[1]).to.equalTime(period2[1]); // upper bound
expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive expect(newUser.course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive
}); });
}); });
...@@ -812,83 +676,83 @@ if (dialect.match(/^postgres/)) { ...@@ -812,83 +676,83 @@ if (dialect.match(/^postgres/)) {
}); });
it('should update range correctly and return the affected rows', function () { it('should update range correctly and return the affected rows', function () {
var User = this.User const User = this.User;
, period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; const period = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
return User.create({ return User.create({
username: 'user', username: 'user',
email: ['foo@bar.com'], email: ['foo@bar.com'],
course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)] course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)]
}).then(function (oldUser) { }).then(oldUser => {
// Update the user and check that the returned object's fields have been parsed by the range parser // Update the user and check that the returned object's fields have been parsed by the range parser
return User.update({ course_period: period }, { where: oldUser.where(), returning: true }) return User.update({ course_period: period }, { where: oldUser.where(), returning: true })
.spread(function (count, users) { .spread(function (count, users) {
expect(count).to.equal(1); expect(count).to.equal(1);
expect(users[0].course_period[0] instanceof Date).to.be.ok; expect(users[0].course_period[0] instanceof Date).to.be.ok;
expect(users[0].course_period[1] instanceof Date).to.be.ok; expect(users[0].course_period[1] instanceof Date).to.be.ok;
expect(users[0].course_period[0]).to.equalTime(period[0]); // lower bound expect(users[0].course_period[0]).to.equalTime(period[0]); // lower bound
expect(users[0].course_period[1]).to.equalTime(period[1]); // upper bound expect(users[0].course_period[1]).to.equalTime(period[1]); // upper bound
expect(users[0].course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive expect(users[0].course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive
}); });
}); });
}); });
it('should read range correctly', function() { it('should read range correctly', function () {
var User = this.User; const User = this.User;
var course_period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; const course_period = [new Date(2015, 1, 1), new Date(2015, 10, 30)];
course_period.inclusive = [false, false]; course_period.inclusive = [false, false];
var data = { username: 'user', email: ['foo@bar.com'], course_period: course_period}; const data = { username: 'user', email: ['foo@bar.com'], course_period: course_period };
return User.create(data) return User.create(data)
.then(function() { .then(() => {
return User.find({ where: { username: 'user' }}); return User.find({ where: { username: 'user' } });
}) })
.then(function(user) { .then(user => {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
expect(user.course_period).to.deep.equal(data.course_period); expect(user.course_period).to.deep.equal(data.course_period);
}); });
}); });
it('should read range array correctly', function() { it('should read range array correctly', function () {
var User = this.User, const User = this.User;
holidays = [ const holidays = [
[new Date(2015, 3, 1, 10), new Date(2015, 3, 15)], [new Date(2015, 3, 1, 10), new Date(2015, 3, 15)],
[new Date(2015, 8, 1), new Date(2015, 9, 15)] [new Date(2015, 8, 1), new Date(2015, 9, 15)]
]; ];
holidays[0].inclusive = [true, true]; holidays[0].inclusive = [true, true];
holidays[1].inclusive = [true, true]; holidays[1].inclusive = [true, true];
var data = { username: 'user', email: ['foo@bar.com'], holidays: holidays }; const data = { username: 'user', email: ['foo@bar.com'], holidays: holidays };
return User.create(data) return User.create(data)
.then(function() { .then(() => {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
return User.find({ where: { username: 'user' }}); return User.find({ where: { username: 'user' } });
}).then(function(user) { }).then(user => {
expect(user.holidays).to.deep.equal(data.holidays); expect(user.holidays).to.deep.equal(data.holidays);
}); });
}); });
it('should read range correctly from multiple rows', function() { it('should read range correctly from multiple rows', function () {
var User = this.User, const User = this.User;
periods = [ const periods = [
[new Date(2015, 0, 1), new Date(2015, 11, 31)], [new Date(2015, 0, 1), new Date(2015, 11, 31)],
[new Date(2016, 0, 1), new Date(2016, 11, 31)] [new Date(2016, 0, 1), new Date(2016, 11, 31)]
]; ];
return User return User
.create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0]}) .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0] })
.then(function() { .then(() => {
return User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1]}); return User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1] });
}) })
.then(function() { .then(() => {
// Check that the range fields are the same when retrieving the user // Check that the range fields are the same when retrieving the user
return User.findAll({ order: ['username'] }); return User.findAll({ order: ['username'] });
}) })
.then(function(users) { .then(users => {
expect(users[0].course_period[0]).to.equalTime(periods[0][0]); // lower bound expect(users[0].course_period[0]).to.equalTime(periods[0][0]); // lower bound
expect(users[0].course_period[1]).to.equalTime(periods[0][1]); // upper bound expect(users[0].course_period[1]).to.equalTime(periods[0][1]); // upper bound
expect(users[0].course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive expect(users[0].course_period.inclusive).to.deep.equal([true, false]); // inclusive, exclusive
...@@ -899,30 +763,29 @@ if (dialect.match(/^postgres/)) { ...@@ -899,30 +763,29 @@ if (dialect.match(/^postgres/)) {
}); });
it('should read range correctly from included models as well', function () { it('should read range correctly from included models as well', function () {
var self = this const period = [new Date(2016, 0, 1), new Date(2016, 11, 31)];
, period = [new Date(2016, 0, 1), new Date(2016, 11, 31)] const HolidayDate = this.sequelize.define('holidayDate', {
, HolidayDate = this.sequelize.define('holidayDate', { period: DataTypes.RANGE(DataTypes.DATE)
period: DataTypes.RANGE(DataTypes.DATE) });
});
self.User.hasMany(HolidayDate); this.User.hasMany(HolidayDate);
return self.sequelize return this.sequelize
.sync({ force: true }) .sync({ force: true })
.then(function () { .then(() => {
return self.User return this.User
.create({ username: 'user', email: ['foo@bar.com'] }) .create({ username: 'user', email: ['foo@bar.com'] })
.then(function (user) { .then(user => {
return HolidayDate.create({ period: period }) return HolidayDate.create({ period: period })
.then(function (holidayDate) { .then(holidayDate => {
return user.setHolidayDates([holidayDate]); return user.setHolidayDates([holidayDate]);
}); });
}); });
}) })
.then(function () { .then(() => {
return self.User.find({ where: { username: 'user' }, include: [HolidayDate] }); return this.User.find({ where: { username: 'user' }, include: [HolidayDate] });
}) })
.then(function (user) { .then(user => {
expect(user.hasOwnProperty('holidayDates')).to.be.ok; expect(user.hasOwnProperty('holidayDates')).to.be.ok;
expect(user.holidayDates.length).to.equal(1); expect(user.holidayDates.length).to.equal(1);
expect(user.holidayDates[0].period.length).to.equal(2); expect(user.holidayDates[0].period.length).to.equal(2);
...@@ -932,38 +795,37 @@ if (dialect.match(/^postgres/)) { ...@@ -932,38 +795,37 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should save geometry correctly', function() { it('should save geometry correctly', function () {
var point = { type: 'Point', coordinates: [39.807222,-76.984722] }; const point = { type: 'Point', coordinates: [39.807222, -76.984722] };
return this.User.create({ username: 'user', email: ['foo@bar.com'], location: point}).then(function(newUser) { return this.User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(newUser => {
expect(newUser.location).to.deep.eql(point); expect(newUser.location).to.deep.eql(point);
}); });
}); });
it('should update geometry correctly', function() { it('should update geometry correctly', function () {
var User = this.User; const User = this.User;
var point1 = { type: 'Point', coordinates: [39.807222,-76.984722] } const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] };
, point2 = { type: 'Point', coordinates: [39.828333,-77.232222] }; const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] };
return User.create({ username: 'user', email: ['foo@bar.com'], location: point1}).then(function(oldUser) { return User.create({ username: 'user', email: ['foo@bar.com'], location: point1 }).then(oldUser => {
return User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }).spread(function(count, updatedUsers) { return User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }).spread(function (count, updatedUsers) {
expect(updatedUsers[0].location).to.deep.eql(point2); expect(updatedUsers[0].location).to.deep.eql(point2);
}); });
}); });
}); });
it('should read geometry correctly', function() { it('should read geometry correctly', function () {
var User = this.User; const User = this.User;
var point = { type: 'Point', coordinates: [39.807222,-76.984722] }; const point = { type: 'Point', coordinates: [39.807222, -76.984722] };
return User.create({ username: 'user', email: ['foo@bar.com'], location: point}).then(function(user) { return User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(user => {
return User.find({ where: { username: user.username }}); return User.find({ where: { username: user.username } });
}).then(function(user) { }).then(user => {
expect(user.location).to.deep.eql(point); expect(user.location).to.deep.eql(point);
}); });
}); });
describe('[POSTGRES] Unquoted identifiers', function() { describe('[POSTGRES] Unquoted identifiers', function () {
it('can insert and select', function() { it('can insert and select', function () {
var self = this;
this.sequelize.options.quoteIdentifiers = false; this.sequelize.options.quoteIdentifiers = false;
this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false;
...@@ -974,10 +836,10 @@ if (dialect.match(/^postgres/)) { ...@@ -974,10 +836,10 @@ if (dialect.match(/^postgres/)) {
quoteIdentifiers: false quoteIdentifiers: false
}); });
return this.User.sync({ force: true }).then(function() { return this.User.sync({ force: true }).then(() => {
return self.User return this.User
.create({ username: 'user', fullName: 'John Smith' }) .create({ username: 'user', fullName: 'John Smith' })
.then(function(user) { .then(user => {
// We can insert into a table with non-quoted identifiers // We can insert into a table with non-quoted identifiers
expect(user.id).to.exist; expect(user.id).to.exist;
expect(user.id).not.to.be.null; expect(user.id).not.to.be.null;
...@@ -985,34 +847,34 @@ if (dialect.match(/^postgres/)) { ...@@ -985,34 +847,34 @@ if (dialect.match(/^postgres/)) {
expect(user.fullName).to.equal('John Smith'); expect(user.fullName).to.equal('John Smith');
// We can query by non-quoted identifiers // We can query by non-quoted identifiers
return self.User.find({ return this.User.find({
where: {fullName: 'John Smith'} where: { fullName: 'John Smith' }
}) }).then(user2 => {
.then(function(user2) {
// We can map values back to non-quoted identifiers // We can map values back to non-quoted identifiers
expect(user2.id).to.equal(user.id); expect(user2.id).to.equal(user.id);
expect(user2.username).to.equal('user'); expect(user2.username).to.equal('user');
expect(user2.fullName).to.equal('John Smith'); expect(user2.fullName).to.equal('John Smith');
// We can query and aggregate by non-quoted identifiers // We can query and aggregate by non-quoted identifiers
return self.User return this.User
.count({ .count({
where: {fullName: 'John Smith'} where: { fullName: 'John Smith' }
}) })
.then(function(count) { .then(count => {
self.sequelize.options.quoteIndentifiers = true; this.sequelize.options.quoteIndentifiers = true;
self.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true;
self.sequelize.options.logging = false; this.sequelize.options.logging = false;
expect(count).to.equal(1); expect(count).to.equal(1);
}); });
}); });
}); });
}); });
}); });
it('can select nested include', function() {
it('can select nested include', function () {
this.sequelize.options.quoteIdentifiers = false; this.sequelize.options.quoteIdentifiers = false;
this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false;
this.Professor = this.sequelize.define('Professor', { this.Professor = this.sequelize.define('Professor', {
fullName: DataTypes.STRING fullName: DataTypes.STRING
}, { }, {
quoteIdentifiers: false quoteIdentifiers: false
...@@ -1030,35 +892,35 @@ if (dialect.match(/^postgres/)) { ...@@ -1030,35 +892,35 @@ if (dialect.match(/^postgres/)) {
this.ClassStudent = this.sequelize.define('ClassStudent', { this.ClassStudent = this.sequelize.define('ClassStudent', {
}, { }, {
quoteIdentifiers: false, quoteIdentifiers: false,
tableName: 'class_student' tableName: 'class_student'
}); });
this.Professor.hasMany(this.Class); this.Professor.hasMany(this.Class);
this.Class.belongsTo(this.Professor); this.Class.belongsTo(this.Professor);
this.Class.belongsToMany(this.Student,{through: this.ClassStudent}); this.Class.belongsToMany(this.Student, { through: this.ClassStudent });
this.Student.belongsToMany(this.Class,{through: this.ClassStudent}); this.Student.belongsToMany(this.Class, { through: this.ClassStudent });
return this.Professor.sync({ force: true }) return this.Professor.sync({ force: true })
.then(()=> { .then(() => {
return this.Student.sync({ force: true }); return this.Student.sync({ force: true });
}) })
.then(()=> { .then(() => {
return this.Class.sync({ force: true }); return this.Class.sync({ force: true });
}) })
.then(()=> { .then(() => {
return this.ClassStudent.sync({ force: true }); return this.ClassStudent.sync({ force: true });
}) })
.then(()=> { .then(() => {
return this.Professor.bulkCreate([ return this.Professor.bulkCreate([
{ {
id: 1, id: 1,
fullName: 'Albus Dumbledore' fullName: 'Albus Dumbledore'
}, },
{ {
id: 2, id: 2,
fullName: 'Severus Snape' fullName: 'Severus Snape'
} }
]); ]);
}) })
.then(()=> { .then(() => {
return this.Class.bulkCreate([ return this.Class.bulkCreate([
{ {
id: 1, id: 1,
...@@ -1077,7 +939,7 @@ if (dialect.match(/^postgres/)) { ...@@ -1077,7 +939,7 @@ if (dialect.match(/^postgres/)) {
} }
]); ]);
}) })
.then(()=> { .then(() => {
return this.Student.bulkCreate([ return this.Student.bulkCreate([
{ {
id: 1, id: 1,
...@@ -1097,29 +959,29 @@ if (dialect.match(/^postgres/)) { ...@@ -1097,29 +959,29 @@ if (dialect.match(/^postgres/)) {
} }
]); ]);
}) })
.then(()=> { .then(() => {
return Promise.all([ return Promise.all([
this.Student.findById(1) this.Student.findById(1)
.then((Harry)=> { .then((Harry) => {
return Harry.setClasses([1,2,3]); return Harry.setClasses([1, 2, 3]);
}), }),
this.Student.findById(2) this.Student.findById(2)
.then((Ron)=> { .then((Ron) => {
return Ron.setClasses([1,2]); return Ron.setClasses([1, 2]);
}), }),
this.Student.findById(3) this.Student.findById(3)
.then((Ginny)=> { .then((Ginny) => {
return Ginny.setClasses([2,3]); return Ginny.setClasses([2, 3]);
}), }),
this.Student.findById(4) this.Student.findById(4)
.then((Hermione)=> { .then((Hermione) => {
return Hermione.setClasses([1,2,3]); return Hermione.setClasses([1, 2, 3]);
}) })
]); ]);
}) })
.then(()=> { .then(() => {
return this.Professor.findAll({ return this.Professor.findAll({
include:[ include: [
{ {
model: this.Class, model: this.Class,
include: [ include: [
...@@ -1136,13 +998,13 @@ if (dialect.match(/^postgres/)) { ...@@ -1136,13 +998,13 @@ if (dialect.match(/^postgres/)) {
] ]
}); });
}) })
.then((professors)=> { .then((professors) => {
expect(professors.length).to.eql(2); expect(professors.length).to.eql(2);
expect(professors[0].fullName).to.eql('Albus Dumbledore'); expect(professors[0].fullName).to.eql('Albus Dumbledore');
expect(professors[0].Classes.length).to.eql(1); expect(professors[0].Classes.length).to.eql(1);
expect(professors[0].Classes[0].Students.length).to.eql(3); expect(professors[0].Classes[0].Students.length).to.eql(3);
}) })
.finally(()=> { .finally(() => {
this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true;
}); });
}); });
......
'use strict'; 'use strict';
var chai = require('chai') const chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../../support') , Support = require(__dirname + '/../../support')
, DataTypes = require(__dirname + '/../../../../lib/data-types') , Sequelize = Support.Sequelize
, dialect = Support.getTestDialect(); , dialect = Support.getTestDialect()
, DataTypes = require(__dirname + '/../../../../lib/data-types');
if (dialect === 'sqlite') { if (dialect === 'sqlite') {
describe('[SQLITE Specific] DAO', function() { describe('[SQLITE Specific] DAO', function () {
beforeEach(function() { beforeEach(function () {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
emergency_contact: DataTypes.JSON,
emergencyContact: DataTypes.JSON,
dateField: { dateField: {
type: DataTypes.DATE, type: DataTypes.DATE,
field: 'date_field' field: 'date_field'
...@@ -27,60 +30,87 @@ if (dialect === 'sqlite') { ...@@ -27,60 +30,87 @@ if (dialect === 'sqlite') {
return this.sequelize.sync({ force: true }); return this.sequelize.sync({ force: true });
}); });
describe('findAll', function() { describe('findAll', function () {
it('handles dates correctly', function() { it('handles dates correctly', function () {
var self = this const user = this.User.build({ username: 'user' });
, user = this.User.build({ username: 'user' });
user.dataValues.createdAt = new Date(2011, 4, 4); user.dataValues.createdAt = new Date(2011, 4, 4);
return user.save().then(function() { return user.save().then(() => {
return self.User.create({ username: 'new user' }).then(function() { return this.User.create({ username: 'new user' }).then(() => {
return self.User.findAll({ return this.User.findAll({
where: { createdAt: { $gt: new Date(2012, 1, 1) }} where: { createdAt: { $gt: new Date(2012, 1, 1) } }
}).then(function(users) { }).then(users => {
expect(users).to.have.length(1); expect(users).to.have.length(1);
}); });
}); });
}); });
}); });
it('handles dates with aliasses correctly #3611', function() { it('handles dates with aliasses correctly #3611', function () {
return this.User.create({ return this.User.create({
dateField: new Date(2010, 10, 10) dateField: new Date(2010, 10, 10)
}).bind(this).then(function () { }).then(() => {
return this.User.findAll().get(0); return this.User.findAll().get(0);
}).then(function (user) { }).then(user => {
expect(user.get('dateField')).to.be.an.instanceof(Date); expect(user.get('dateField')).to.be.an.instanceof(Date);
expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10));
}); });
}); });
it('handles dates in includes correctly #2644', function() { it('handles dates in includes correctly #2644', function () {
return this.User.create({ return this.User.create({
projects: [ projects: [
{ dateField: new Date(1990, 5, 5) } { dateField: new Date(1990, 5, 5) }
] ]
}, { include: [this.Project]}).bind(this).then(function () { }, { include: [this.Project] }).then(() => {
return this.User.findAll({ return this.User.findAll({
include: [this.Project] include: [this.Project]
}).get(0); }).get(0);
}).then(function (user) { }).then(user => {
expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date);
expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5));
}); });
}); });
}); });
describe('regression tests', function() { describe('json', function () {
it('should be able to retrieve a row with json_extract function', function () {
it('do not crash while parsing unique constraint errors', function() { return this.sequelize.Promise.all([
var Payments = this.sequelize.define('payments', {}); this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })
]).then(() => {
return this.User.find({
where: Sequelize.json(`json_extract(emergency_contact, '$.name')`, 'kate'),
attributes: ['username', 'emergency_contact']
});
}).then(user => {
expect(user.emergency_contact.name).to.equal('kate');
});
});
return Payments.sync({force: true}).then(function () { it('should be able to retrieve a row by json_type function', function () {
return (expect(Payments.bulkCreate([{id: 1}, {id: 1}], { ignoreDuplicates: false })).to.eventually.be.rejected); return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: ['kate', 'joe'] })
]).then(() => {
return this.User.find({
where: Sequelize.json(`json_type(emergency_contact)`, 'array'),
attributes: ['username', 'emergency_contact']
});
}).then(user => {
expect(user.username).to.equal('anna');
}); });
});
});
describe('regression tests', function () {
it('do not crash while parsing unique constraint errors', function () {
const Payments = this.sequelize.define('payments', {});
return Payments.sync({ force: true }).then(() => {
return (expect(Payments.bulkCreate([{ id: 1 }, { id: 1 }], { ignoreDuplicates: false })).to.eventually.be.rejected);
});
}); });
}); });
}); });
......
'use strict';
const chai = require('chai')
, expect = chai.expect
, Support = require('./support')
, Sequelize = Support.Sequelize
, current = Support.sequelize
, DataTypes = Sequelize.DataTypes;
describe('model', function () {
if (current.dialect.supports.JSON) {
describe('json', function () {
beforeEach(function () {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
emergency_contact: DataTypes.JSON,
emergencyContact: DataTypes.JSON,
});
return this.sequelize.sync({ force: true });
});
it('should tell me that a column is json', function () {
return this.sequelize.queryInterface.describeTable('Users')
.then(table => {
expect(table.emergency_contact.type).to.equal('JSON');
});
});
it('should stringify json with insert', function () {
return this.User.create({
username: 'bob',
emergency_contact: { name: 'joe', phones: [1337, 42] }
}, {
fields: ['id', 'username', 'document', 'emergency_contact'],
logging: sql => {
const expected = '\'{"name":"joe","phones":[1337,42]}\'';
expect(sql.indexOf(expected)).not.to.equal(-1);
}
});
});
it('should insert json using a custom field name', function () {
this.UserFields = this.sequelize.define('UserFields', {
emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' }
});
return this.UserFields.sync({ force: true }).then(() => {
return this.UserFields.create({
emergencyContact: { name: 'joe', phones: [1337, 42] }
}).then(user => {
expect(user.emergencyContact.name).to.equal('joe');
});
});
});
it('should update json using a custom field name', function () {
this.UserFields = this.sequelize.define('UserFields', {
emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' }
});
return this.UserFields.sync({ force: true }).then(() => {
return this.UserFields.create({
emergencyContact: { name: 'joe', phones: [1337, 42] }
}).then(user => {
user.emergencyContact = { name: 'larry' };
return user.save();
}).then(user => {
expect(user.emergencyContact.name).to.equal('larry');
});
});
});
it('should be able retrieve json value as object', function () {
const emergencyContact = { name: 'kate', phone: 1337 };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(user => {
expect(user.emergency_contact).to.eql(emergencyContact);
return this.User.find({ where: { username: 'swen' }, attributes: ['emergency_contact'] });
})
.then(user => {
expect(user.emergency_contact).to.eql(emergencyContact);
});
});
it('should be able to retrieve element of array by index', function () {
const emergencyContact = { name: 'kate', phones: [1337, 42] };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(user => {
expect(user.emergency_contact).to.eql(emergencyContact);
return this.User.find({
where: { username: 'swen' },
attributes: [[Sequelize.json('emergency_contact.phones[1]'), 'firstEmergencyNumber']]
});
})
.then(user => {
expect(parseInt(user.getDataValue('firstEmergencyNumber'))).to.equal(42);
});
});
it('should be able to retrieve root level value of an object by key', function () {
const emergencyContact = { kate: 1337 };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(user => {
expect(user.emergency_contact).to.eql(emergencyContact);
return this.User.find({
where: { username: 'swen' },
attributes: [[Sequelize.json('emergency_contact.kate'), 'katesNumber']]
});
})
.then(user => {
expect(parseInt(user.getDataValue('katesNumber'))).to.equal(1337);
});
});
it('should be able to retrieve nested value of an object by path', function () {
const emergencyContact = { kate: { email: 'kate@kate.com', phones: [1337, 42] } };
return this.User.create({ username: 'swen', emergency_contact: emergencyContact })
.then(user => {
expect(user.emergency_contact).to.eql(emergencyContact);
return this.User.find({
where: { username: 'swen' },
attributes: [[Sequelize.json('emergency_contact.kate.email'), 'katesEmail']]
});
}).then(user => {
expect(user.getDataValue('katesEmail')).to.equal('kate@kate.com');
}).then(() => {
return this.User.find({
where: { username: 'swen' },
attributes: [[Sequelize.json('emergency_contact.kate.phones[1]'), 'katesFirstPhone']]
});
}).then(user => {
expect(parseInt(user.getDataValue('katesFirstPhone'))).to.equal(42);
});
});
it('should be able to retrieve a row based on the values of the json document', function () {
return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })
]).then(() => {
return this.User.find({
where: Sequelize.json('emergency_contact.name', 'kate'),
attributes: ['username', 'emergency_contact']
});
}).then(user => {
expect(user.emergency_contact.name).to.equal('kate');
});
});
it('should be able to query using the nested query language', function () {
return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })
]).then(() => {
return this.User.find({
where: Sequelize.json({ emergency_contact: { name: 'kate' } })
});
}).then(user => {
expect(user.emergency_contact.name).to.equal('kate');
});
});
it('should be able to query using dot notation', function () {
return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })
]).then(() => {
return this.User.find({ where: Sequelize.json('emergency_contact.name', 'joe') });
}).then(user => {
expect(user.emergency_contact.name).to.equal('joe');
});
});
it('should be able to query using dot notation with uppercase name', function () {
return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }),
this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })
]).then(() => {
return this.User.find({
attributes: [[Sequelize.json('emergencyContact.name'), 'contactName']],
where: Sequelize.json('emergencyContact.name', 'joe')
});
}).then(user => {
expect(user.get('contactName')).to.equal('joe');
});
});
it('should be able to query array using property accessor', function () {
return this.sequelize.Promise.all([
this.User.create({ username: 'swen', emergency_contact: ['kate', 'joe'] }),
this.User.create({ username: 'anna', emergency_contact: [{ name: 'joe' }] })
]).then(() => {
return this.User.find({ where: Sequelize.json('emergency_contact.0', 'kate') });
}).then(user => {
expect(user.username).to.equal('swen');
}).then(() => {
return this.User.find({ where: Sequelize.json('emergency_contact[0].name', 'joe') });
}).then(user => {
expect(user.username).to.equal('anna');
});
});
it('should be able to store values that require JSON escaping', function () {
const text = `Multi-line '$string' needing "escaping" for $$ and $1 type values`;
return this.User.create({
username: 'swen',
emergency_contact: { value: text }
}).then(user => {
expect(user.isNewRecord).to.equal(false);
}).then(() => {
return this.User.find({ where: { username: 'swen' } });
}).then(() => {
return this.User.find({ where: Sequelize.json('emergency_contact.value', text) });
}).then(user => {
expect(user.username).to.equal('swen');
});
});
it('should be able to findOrCreate with values that require JSON escaping', function () {
const text = `Multi-line '$string' needing "escaping" for $$ and $1 type values`;
return this.User.findOrCreate({
where: { username: 'swen' },
defaults: { emergency_contact: { value: text } }
}).then(user => {
expect(!user.isNewRecord).to.equal(true);
}).then(() => {
return this.User.find({ where: { username: 'swen' } });
}).then(() => {
return this.User.find({ where: Sequelize.json('emergency_contact.value', text) });
}).then(user => {
expect(user.username).to.equal('swen');
});
});
});
}
});
'use strict'; 'use strict';
/* jshint -W030 */ /* jshint -W030 */
/* jshint -W079 */
/* jshint -W110 */ /* jshint -W110 */
var chai = require('chai') const chai = require('chai')
, Sequelize = require('../../../index') , Sequelize = require('../../../index')
, Promise = Sequelize.Promise , Promise = Sequelize.Promise
, expect = chai.expect , expect = chai.expect
...@@ -10,27 +11,26 @@ var chai = require('chai') ...@@ -10,27 +11,26 @@ var chai = require('chai')
, DataTypes = require(__dirname + '/../../../lib/data-types') , DataTypes = require(__dirname + '/../../../lib/data-types')
, current = Support.sequelize; , current = Support.sequelize;
describe(Support.getTestDialectTeaser('Model'), function() { describe(Support.getTestDialectTeaser('Model'), function () {
if (current.dialect.supports.JSONB) { if (current.dialect.supports.JSON) {
describe('JSONB', function () { describe('JSON', function () {
beforeEach(function () { beforeEach(function () {
this.Event = this.sequelize.define('Event', { this.Event = this.sequelize.define('Event', {
data: { data: {
type: DataTypes.JSONB, type: DataTypes.JSON,
field: 'event_data', field: 'event_data',
index: true index: true
}, },
json: DataTypes.JSON json: DataTypes.JSON
}); });
return this.Event.sync({force: true}); return this.Event.sync({ force: true });
}); });
if (current.dialect.supports.lock) { if (current.dialect.supports.lock) {
it('findOrCreate supports transactions, json and locks', function() { it('findOrCreate supports transactions, json and locks', function () {
var self = this; return current.transaction().then(transaction => {
return current.transaction().then(function(t) { return this.Event.findOrCreate({
return self.Event.findOrCreate({
where: { where: {
json: { some: { input: 'Hello' } } json: { some: { input: 'Hello' } }
}, },
...@@ -38,18 +38,18 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -38,18 +38,18 @@ describe(Support.getTestDialectTeaser('Model'), function() {
json: { some: { input: 'Hello' }, input: [1, 2, 3] }, json: { some: { input: 'Hello' }, input: [1, 2, 3] },
data: { some: { input: 'There' }, input: [4, 5, 6] } data: { some: { input: 'There' }, input: [4, 5, 6] }
}, },
transaction: t, transaction: transaction,
lock: t.LOCK.UPDATE, lock: transaction.LOCK.UPDATE,
logging: function (sql) { logging: sql => {
if (sql.indexOf('SELECT') !== -1 && sql.indexOf('CREATE') === -1) { if (sql.indexOf('SELECT') !== -1 && sql.indexOf('CREATE') === -1) {
expect(sql.indexOf('FOR UPDATE')).not.to.be.equal(-1); expect(sql.indexOf('FOR UPDATE')).not.to.be.equal(-1);
} }
} }
}).then(function() { }).then(() => {
return self.Event.count().then(function(count) { return this.Event.count().then(count => {
expect(count).to.equal(0); expect(count).to.equal(0);
return t.commit().then(function() { return transaction.commit().then(() => {
return self.Event.count().then(function(count) { return this.Event.count().then(count => {
expect(count).to.equal(1); expect(count).to.equal(1);
}); });
}); });
...@@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
} }
it('should create an instance with JSONB data', function () { it('should create an instance with JSON data', function () {
return this.Event.create({ return this.Event.create({
data: { data: {
name: { name: {
...@@ -68,9 +68,9 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -68,9 +68,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
} }
}).bind(this).then(function () { }).then(() => {
return this.Event.findAll().then(function (events) { return this.Event.findAll().then(events => {
var event = events[0]; const event = events[0];
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
name: { name: {
...@@ -83,7 +83,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -83,7 +83,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
}); });
it('should update an instance with JSONB data', function () { it('should update an instance with JSON data', function () {
return this.Event.create({ return this.Event.create({
data: { data: {
name: { name: {
...@@ -92,7 +92,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -92,7 +92,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
} }
}).bind(this).then(function (event) { }).then(event => {
return event.update({ return event.update({
data: { data: {
name: { name: {
...@@ -102,9 +102,9 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -102,9 +102,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
employment: null employment: null
} }
}); });
}).then(function () { }).then(() => {
return this.Event.findAll().then(function (events) { return this.Event.findAll().then(events => {
var event = events[0]; const event = events[0];
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
name: { name: {
...@@ -137,15 +137,15 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -137,15 +137,15 @@ describe(Support.getTestDialectTeaser('Model'), function() {
employment: 'Housewife' employment: 'Housewife'
} }
}) })
).bind(this).then(function () { ).then(() => {
return this.Event.findAll({ return this.Event.findAll({
where: { where: {
data: { data: {
employment: 'Housewife' employment: 'Housewife'
} }
} }
}).then(function (events) { }).then(events => {
var event = events[0]; const event = events[0];
expect(events.length).to.equal(1); expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
...@@ -179,7 +179,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -179,7 +179,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
age: 37 age: 37
} }
}) })
).bind(this).then(function () { ).then(() => {
return this.Event.findAll({ return this.Event.findAll({
where: { where: {
data: { data: {
...@@ -188,8 +188,8 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -188,8 +188,8 @@ describe(Support.getTestDialectTeaser('Model'), function() {
} }
} }
} }
}).then(function (events) { }).then(events => {
var event = events[0]; const event = events[0];
expect(events.length).to.equal(1); expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
...@@ -223,14 +223,14 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -223,14 +223,14 @@ describe(Support.getTestDialectTeaser('Model'), function() {
employment: null employment: null
} }
}) })
).bind(this).then(function () { ).then(() => {
return this.Event.findAll({ return this.Event.findAll({
where: { where: {
data: { data: {
employment: null employment: null
} }
} }
}).then(function (events) { }).then(events => {
expect(events.length).to.equal(1); expect(events.length).to.equal(1);
expect(events[0].get('data')).to.eql({ expect(events[0].get('data')).to.eql({
name: { name: {
...@@ -244,7 +244,6 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -244,7 +244,6 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
it('should be possible to query multiple nested values', function () { it('should be possible to query multiple nested values', function () {
var self = this;
return this.Event.create({ return this.Event.create({
data: { data: {
name: { name: {
...@@ -253,9 +252,9 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -253,9 +252,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
} }
}).then(function() { }).then(() => {
return Promise.join( return Promise.join(
self.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Marge', first: 'Marge',
...@@ -264,7 +263,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -264,7 +263,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
employment: 'Housewife' employment: 'Housewife'
} }
}), }),
self.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Bart', first: 'Bart',
...@@ -274,8 +273,8 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -274,8 +273,8 @@ describe(Support.getTestDialectTeaser('Model'), function() {
} }
}) })
); );
}).then(function () { }).then(() => {
return self.Event.findAll({ return this.Event.findAll({
where: { where: {
data: { data: {
name: { name: {
...@@ -289,7 +288,7 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -289,7 +288,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
order: [ order: [
['id', 'ASC'] ['id', 'ASC']
] ]
}).then(function (events) { }).then(events => {
expect(events.length).to.equal(2); expect(events.length).to.equal(2);
expect(events[0].get('data')).to.eql({ expect(events[0].get('data')).to.eql({
...@@ -312,10 +311,10 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -312,10 +311,10 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}); });
it('should be possible to destroy with where', function () { it('should be possible to destroy with where', function () {
var conditionSearch = { const conditionSearch = {
where: { where: {
data: { data: {
employment : 'Hacker' employment: 'Hacker'
} }
} }
}; };
...@@ -348,15 +347,72 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -348,15 +347,72 @@ describe(Support.getTestDialectTeaser('Model'), function() {
employment: 'CTO' employment: 'CTO'
} }
}) })
).bind(this).then(function () { ).then(() => {
return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2);
}).then(function() { }).then(() => {
return this.Event.destroy(conditionSearch); return this.Event.destroy(conditionSearch);
}).then(function(){ }).then(() => {
return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0);
}); });
}); });
describe('sql injection attacks', function () {
beforeEach(function () {
this.Model = this.sequelize.define('Model', {
data: DataTypes.JSON
});
return this.sequelize.sync({ force: true });
});
it('should properly escape the single quotes', function () {
return this.Model.create({
data: {
type: 'Point',
properties: {
exploit: "'); DELETE YOLO INJECTIONS; -- "
}
}
});
});
it('should properly escape the single quotes in array', function () {
return this.Model.create({
data: {
type: 'Point',
coordinates: [39.807222, "'); DELETE YOLO INJECTIONS; --"]
}
});
});
it('should be possible to find with properly escaped select query', function () {
return this.Model.create({
data: {
type: 'Point',
properties: {
exploit: "'); DELETE YOLO INJECTIONS; -- "
},
}
}).then(() => {
return this.Model.findOne({
where: {
data: {
type: 'Point',
properties: {
exploit: "'); DELETE YOLO INJECTIONS; -- "
},
}
}
});
}).then(result => {
expect(result.get('data')).to.deep.equal({
type: 'Point',
properties: {
exploit: "'); DELETE YOLO INJECTIONS; -- "
}
});
});
});
});
}); });
} }
}); });
...@@ -196,13 +196,13 @@ describe(Support.getTestDialectTeaser('Utils'), function() { ...@@ -196,13 +196,13 @@ describe(Support.getTestDialectTeaser('Utils'), function() {
}, },
another_json_field: { x: 1 } another_json_field: { x: 1 }
}; };
var expected = `"metadata"#>>'{language}' = 'icelandic' and "metadata"#>>'{pg_rating,dk}' = 'G' and "another_json_field"#>>'{x}' = '1'`; var expected = `("metadata"#>>'{language}') = 'icelandic' AND ("metadata"#>>'{pg_rating,dk}') = 'G' AND ("another_json_field"#>>'{x}') = '1'`;
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(conditions))).to.deep.equal(expected); expect(queryGenerator.handleSequelizeMethod(new Utils.Json(conditions))).to.deep.equal(expected);
}); });
it('successfully parses a string using dot notation', function() { it('successfully parses a string using dot notation', function() {
var path = 'metadata.pg_rating.dk'; var path = 'metadata.pg_rating.dk';
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal(`"metadata"#>>'{pg_rating,dk}'`); expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal(`("metadata"#>>'{pg_rating,dk}')`);
}); });
it('allows postgres json syntax', function() { it('allows postgres json syntax', function() {
...@@ -213,7 +213,7 @@ describe(Support.getTestDialectTeaser('Utils'), function() { ...@@ -213,7 +213,7 @@ describe(Support.getTestDialectTeaser('Utils'), function() {
it('can take a value to compare against', function() { it('can take a value to compare against', function() {
var path = 'metadata.pg_rating.is'; var path = 'metadata.pg_rating.is';
var value = 'U'; var value = 'U';
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path, value))).to.equal(`"metadata"#>>'{pg_rating,is}' = 'U'`); expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path, value))).to.equal(`("metadata"#>>'{pg_rating,is}') = 'U'`);
}); });
}); });
} }
......
...@@ -200,9 +200,13 @@ var Support = { ...@@ -200,9 +200,13 @@ var Support = {
var expectation = expectations[Support.sequelize.dialect.name]; var expectation = expectations[Support.sequelize.dialect.name];
if (!expectation) { if (!expectation) {
expectation = expectations['default'] if (expectations['default'] !== undefined) {
.replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT) expectation = expectations['default']
.replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT); .replace(/\[/g, Support.sequelize.dialect.TICK_CHAR_LEFT)
.replace(/\]/g, Support.sequelize.dialect.TICK_CHAR_RIGHT);
} else {
throw new Error('Undefined expectation for "' + Support.sequelize.dialect.name + '"!');
}
} }
if (_.isError(query)) { if (_.isError(query)) {
......
...@@ -128,7 +128,7 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -128,7 +128,7 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
}); });
} }
if (current.dialect.supports.JSON) { if (current.dialect.supports.JSONB) {
test('operator', function () { test('operator', function () {
expectsql(sql.addIndexQuery('table', { expectsql(sql.addIndexQuery('table', {
fields: ['event'], fields: ['event'],
......
'use strict'; 'use strict';
var Support = require(__dirname + '/../support') /*jshint -W110 */
const Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + '/../../../lib/data-types') , DataTypes = require(__dirname + '/../../../lib/data-types')
, expect = require('chai').expect
, expectsql = Support.expectsql , expectsql = Support.expectsql
, current = Support.sequelize , Sequelize = Support.Sequelize
, sql = current.dialect.QueryGenerator , current = Support.sequelize
, current = Support.sequelize; , sql = current.dialect.QueryGenerator;
// Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation
if (current.dialect.supports.JSON) { if (current.dialect.supports.JSON) {
suite(Support.getTestDialectTeaser('SQL'), function() { suite(Support.getTestDialectTeaser('SQL'), function () {
suite('JSON', function () { suite('JSON', function () {
suite('escape', function () { suite('escape', function () {
test('plain string', function () { test('plain string', function () {
...@@ -49,24 +51,104 @@ if (current.dialect.supports.JSON) { ...@@ -49,24 +51,104 @@ if (current.dialect.supports.JSON) {
}); });
}); });
test('array of JSON', function () { if (current.dialect.supports.ARRAY) {
expectsql(sql.escape([ test('array of JSON', function () {
{ some: 'nested', more: { nested: true }, answer: 42 }, expectsql(sql.escape([
43, { some: 'nested', more: { nested: true }, answer: 42 },
'joe' 43,
], { type: DataTypes.ARRAY(DataTypes.JSON)}), { 'joe'
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]' ], { type: DataTypes.ARRAY(DataTypes.JSON) }), {
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]'
});
});
if (current.dialect.supports.JSONB) {
test('array of JSONB', function () {
expectsql(sql.escape([
{ some: 'nested', more: { nested: true }, answer: 42 },
43,
'joe'
], { type: DataTypes.ARRAY(DataTypes.JSONB) }), {
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSONB[]'
});
});
}
}
});
suite('path extraction', function () {
test('condition object', function () {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ id: 1 })), {
postgres: `("id"#>>'{}') = '1'`,
sqlite: "json_extract(`id`, '$') = '1'"
});
});
test('nested condition object', function () {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ profile: { id: 1 } })), {
postgres: `("profile"#>>'{id}') = '1'`,
sqlite: "json_extract(`profile`, '$.id') = '1'"
});
});
test('multiple condition object', function () {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: { value: 1 }, another: { value: 'string' } })), {
postgres: `("property"#>>'{value}') = '1' AND ("another"#>>'{value}') = 'string'`,
sqlite: "json_extract(`property`, '$.value') = '1' AND json_extract(`another`, '$.value') = 'string'"
});
});
test('dot notaion', function () {
expectsql(sql.whereItemQuery(Sequelize.json('profile.id'), '1'), {
postgres: `("profile"#>>'{id}') = '1'`,
sqlite: "json_extract(`profile`, '$.id') = '1'"
});
});
test('column named "json"', function () {
expectsql(sql.whereItemQuery(Sequelize.json('json'), '{}'), {
postgres: `("json"#>>'{}') = '{}'`,
sqlite: "json_extract(`json`, '$') = '{}'"
});
});
});
suite('raw json query', function () {
if (current.dialect.name === 'postgres') {
test('#>> operator', function () {
expectsql(sql.whereItemQuery(Sequelize.json(`("data"#>>'{id}')`), 'id'), {
postgres: `("data"#>>'{id}') = 'id'`
});
});
}
test('json function', function () {
expectsql(sql.handleSequelizeMethod(Sequelize.json(`json('{"profile":{"name":"david"}}')`)), {
default: `json('{"profile":{"name":"david"}}')`
}); });
}); });
test('array of JSONB', function () {
expectsql(sql.escape([ test('nested json functions', function () {
{ some: 'nested', more: { nested: true }, answer: 42 }, expectsql(sql.handleSequelizeMethod(Sequelize.json(`json_extract(json_object('{"profile":null}'), "profile")`)), {
43, default: `json_extract(json_object('{"profile":null}'), "profile")`
'joe'
], { type: DataTypes.ARRAY(DataTypes.JSONB)}), {
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSONB[]'
}); });
}); });
test('escaped string argument', function () {
expectsql(sql.handleSequelizeMethod(Sequelize.json(`json('{"quote":{"single":"''","double":""""},"parenthesis":"())("}')`)), {
default: `json('{"quote":{"single":"''","double":""""},"parenthesis":"())("}')`
});
});
test(`unbalnced statement`, function () {
expect(() => sql.handleSequelizeMethod(Sequelize.json('json())'))).to.throw();
expect(() => sql.handleSequelizeMethod(Sequelize.json('json_extract(json()'))).to.throw();
});
test('separator injection', function () {
expect(() => sql.handleSequelizeMethod(Sequelize.json('json(; DELETE YOLO INJECTIONS; -- )'))).to.throw();
expect(() => sql.handleSequelizeMethod(Sequelize.json('json(); DELETE YOLO INJECTIONS; -- '))).to.throw();
});
}); });
}); });
}); });
......
...@@ -717,9 +717,10 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -717,9 +717,10 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
if (current.dialect.supports.JSON) { if (current.dialect.supports.JSON) {
suite('JSON', function () { suite('JSON', function () {
test('sequelize.json("profile->>\'id\', sequelize.cast(2, \'text\')")', function () { test('sequelize.json("profile.id"), sequelize.cast(2, \'text\')")', function () {
expectsql(sql.whereItemQuery(undefined, this.sequelize.json("profile->>'id'", this.sequelize.cast('12346-78912', 'text'))), { expectsql(sql.whereItemQuery(undefined, this.sequelize.json("profile.id", this.sequelize.cast('12346-78912', 'text'))), {
postgres: "profile->>'id' = CAST('12346-78912' AS TEXT)" postgres: "(\"profile\"#>>'{id}') = CAST('12346-78912' AS TEXT)",
sqlite: "json_extract(`profile`, '$.id') = CAST('12346-78912' AS TEXT)"
}); });
}); });
...@@ -733,7 +734,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -733,7 +734,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
}, },
prefix: 'User' prefix: 'User'
}, { }, {
default: "([User].[data]#>>'{nested, attribute}') = 'value'" postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'"
}); });
testsql('data', { testsql('data', {
...@@ -749,7 +751,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -749,7 +751,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
}, },
prefix: 'User' prefix: 'User'
}, { }, {
default: "(([User].[data]#>>'{nested, attribute}') = 'value' AND ([User].[data]#>>'{nested, prop}') != 'None')" postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')"
}); });
testsql('data', { testsql('data', {
...@@ -765,7 +768,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -765,7 +768,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
}, },
prefix: 'User' prefix: 'User'
}, { }, {
default: "(([User].[data]#>>'{name, last}') = 'Simpson' AND ([User].[data]#>>'{employment}') != 'None')" postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
}); });
testsql('data.nested.attribute', 'value', { testsql('data.nested.attribute', 'value', {
...@@ -777,19 +781,21 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -777,19 +781,21 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
} }
} }
}, { }, {
default: "([data]#>>'{nested, attribute}') = 'value'" postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
}); });
testsql('data.nested.attribute', 4, { testsql('data.nested.attribute', 4, {
model: { model: {
rawAttributes: { rawAttributes: {
data: { data: {
type: new DataTypes.JSONB() type: new DataTypes.JSON()
} }
} }
} }
}, { }, {
default: "([data]#>>'{nested, attribute}')::double precision = 4" postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4"
}); });
testsql('data.nested.attribute', { testsql('data.nested.attribute', {
...@@ -803,7 +809,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -803,7 +809,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
} }
} }
}, { }, {
default: "([data]#>>'{nested, attribute}') IN (3, 7)" postgres: "(\"data\"#>>'{nested,attribute}') IN (3, 7)",
sqlite: "json_extract(`data`, '$.nested.attribute') IN (3, 7)"
}); });
testsql('data', { testsql('data', {
...@@ -817,7 +824,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -817,7 +824,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
default: "([data]#>>'{nested, attribute}')::double precision > 2" postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2"
}); });
testsql('data', { testsql('data', {
...@@ -831,7 +839,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -831,7 +839,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
default: "([data]#>>'{nested, attribute}')::integer > 2" postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2"
}); });
var dt = new Date(); var dt = new Date();
...@@ -846,7 +855,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -846,7 +855,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
default: "([data]#>>'{nested, attribute}')::timestamptz > "+sql.escape(dt) postgres: "CAST((\"data\"#>>'{nested,attribute}') AS TIMESTAMPTZ) > "+sql.escape(dt),
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DATETIME) > "+sql.escape(dt)
}); });
testsql('data', { testsql('data', {
...@@ -858,19 +868,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -858,19 +868,8 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
default: "([data]#>>'{nested, attribute}')::boolean = true" postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true",
}); sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1"
testsql('data', {
$contains: {
company: 'Magnafone'
}
}, {
field: {
type: new DataTypes.JSONB()
}
}, {
default: '[data] @> \'{"company":"Magnafone"}\''
}); });
testsql('metaData.nested.attribute', 'value', { testsql('metaData.nested.attribute', 'value', {
...@@ -884,7 +883,24 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -884,7 +883,24 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
} }
} }
}, { }, {
default: "([meta_data]#>>'{nested, attribute}') = 'value'" postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`meta_data`, '$.nested.attribute') = 'value'"
});
});
}
if (current.dialect.supports.JSONB) {
suite('JSONB', function () {
testsql('data', {
$contains: {
company: 'Magnafone'
}
}, {
field: {
type: new DataTypes.JSONB()
}
}, {
default: '[data] @> \'{"company":"Magnafone"}\''
}); });
}); });
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!