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

Commit 719f95b7 by Sushant Committed by GitHub

v5-beta (#9212)

1 parent 3fd065ca
---
verifyConditions:
- "@semantic-release/npm"
- "@semantic-release/github"
publish:
- "@semantic-release/npm"
- "@semantic-release/github"
prepare:
- "@semantic-release/npm"
......@@ -7,6 +7,7 @@ branches:
only:
- master
- v3
- v5
- /^greenkeeper/.*$/
except:
- /^v\d+\.\d+\.\d+$/
......@@ -43,50 +44,42 @@ before_script:
- "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi"
script:
- 'if [ "$INSTALL_MODULE" ]; then npm install ${INSTALL_MODULE}; fi'
- npm run lint
- "if [ $COVERAGE ]; then npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info; else npm run test; fi"
jobs:
include:
- stage: test
node_js: '4'
node_js: '6'
env: DIALECT=sqlite COVERAGE=true
- stage: test
node_js: '4'
node_js: '6'
sudo: required
env: MYSQL_VER=mysql-57 DIALECT=mysql COVERAGE=true
- stage: test
node_js: '4'
node_js: '6'
sudo: required
env: POSTGRES_VER=postgres-95 DIALECT=postgres COVERAGE=true
- stage: test
node_js: '4'
node_js: '6'
sudo: required
env: POSTGRES_VER=postgres-95 DIALECT=postgres-native COVERAGE=true
- stage: test
node_js: '8'
env: DIALECT=sqlite
- stage: test
node_js: '4'
sudo: required
env: POSTGRES_VER=postgres-95 DIALECT=postgres COVERAGE=true INSTALL_MODULE="pg@6.x pg-native@1.x"
- stage: test
node_js: '6'
env: DIALECT=sqlite
- stage: release
node_js: '8'
script:
- npm run semantic-release
before_deploy:
- npm run docs
deploy:
provider: surge
project: ./esdoc/
domain: docs.sequelizejs.com
skip_cleanup: true
# - stage: release
# node_js: '8'
# script:
# - npm run semantic-release
# before_deploy:
# - npm run docs
# deploy:
# provider: surge
# project: ./esdoc/
# domain: docs.sequelizejs.com
# skip_cleanup: true
stages:
- test
- name: release
if: branch = master AND type = push AND fork = false
# - name: release
# if: branch = master AND type = push AND fork = false
......@@ -13,7 +13,7 @@ cache:
environment:
matrix:
- {NODE_VERSION: 4, DIALECT: mssql, COVERAGE: true}
- {NODE_VERSION: 6, DIALECT: mssql, COVERAGE: true}
install:
- ps: Install-Product node $env:NODE_VERSION x64
......@@ -43,3 +43,4 @@ after_test:
branches:
only:
- master
- v5
# Upgrade to v5
Sequelize v5 is the next major release after v4
## Breaking Changes
### Support for Node 6 and up
Sequelize v5 will only support Node 6 and up [#9015](https://github.com/sequelize/sequelize/issues/9015)
__DEV__: _Done, no deprecation_
### Secure Operators
With v4 you started to get a deprecation warning `String based operators are now deprecated`. Also concept of operators was introduced. These operators are Symbols which prevent hash injection attacks.
Please check these threads to know more
- (Issue) https://github.com/sequelize/sequelize/issues/7310
- (Fix) https://github.com/sequelize/sequelize/pull/8240
- (Explanation) https://github.com/sequelize/sequelize/issues/8417#issuecomment-334056048
- (Official Docs) http://docs.sequelizejs.com/manual/tutorial/querying.html#operators-security
With v5
- Operators are now enabled by default.
- You can still use string operators by passing an operators map in `operatorsAliases`
- Op.$raw is removed
__DEV__: _Incomplete, deprecated_
### Model
**Attributes**
`Model.attributes` now removed, use `Model.rawAttributes`. [#5320](https://github.com/sequelize/sequelize/issues/5320)
__Note__: _Please dont confuse this with `options.attributes`, they are still valid_
__DEV__: _Done, no deprecation_
**Paranoid Mode**
With v5 if `deletedAt` is set, record will be considered as deleted. So `paranoid` option will only use `deletedAt` as flag.
In v4 it used to compare current time with `deletedAt`. [#8496](https://github.com/sequelize/sequelize/issues/8496)
__DEV__: _Done, no deprecation_
'use strict';
const Utils = require('../../utils');
const SqlString = require('../../sql-string');
const Model = require('../../model');
const DataTypes = require('../../data-types');
const util = require('util');
const _ = require('lodash');
const Dottie = require('dottie');
const uuid = require('uuid');
const semver = require('semver');
const Utils = require('../../utils');
const SqlString = require('../../sql-string');
const DataTypes = require('../../data-types');
const Model = require('../../model');
const Association = require('../../associations/base');
const BelongsTo = require('../../associations/belongs-to');
const BelongsToMany = require('../../associations/belongs-to-many');
const HasMany = require('../../associations/has-many');
const Op = require('../../operators');
const uuid = require('uuid');
const semver = require('semver');
const QueryGenerator = {
_templateSettings: require('lodash').runInContext().templateSettings,
options: {},
const QuoteHelper = require('./query-generator/helpers/quote');
/**
* Abstract Query Generator
*
* @private
*/
class QueryGenerator {
constructor(options) {
if (!options.sequelize) throw new Error('QueryGenerator initialized without options.sequelize');
if (!options._dialect) throw new Error('QueryGenerator initialized without options._dialect');
this.sequelize = options.sequelize;
this.options = options.sequelize.options;
// dialect name
this.dialect = options._dialect.name;
this._dialect = options._dialect;
// template config
this._templateSettings = require('lodash').runInContext().templateSettings;
}
extractTableDetails(tableName, options) {
options = options || {};
......@@ -27,11 +47,10 @@ const QueryGenerator = {
tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName,
delimiter: tableName.delimiter || options.delimiter || '.'
};
},
}
addSchema(param) {
const self = this;
if (!param._schema) return param.tableName || param;
return {
......@@ -44,11 +63,11 @@ const QueryGenerator = {
return self.quoteTable(this);
}
};
},
}
dropSchema(tableName, options) {
return this.dropTableQuery(tableName, options);
},
}
describeTableQuery(tableName, schema, schemaDelimiter) {
const table = this.quoteTable(
......@@ -59,21 +78,27 @@ const QueryGenerator = {
})
);
return 'DESCRIBE ' + table + ';';
},
return `DESCRIBE ${table};`;
}
dropTableQuery(tableName) {
return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)};`;
},
}
renameTableQuery(before, after) {
return `ALTER TABLE ${this.quoteTable(before)} RENAME TO ${this.quoteTable(after)};`;
},
}
/*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
@private
*/
/**
* Returns an insert into command
*
* @param {String} table
* @param {Object} valueHash attribute value pairs
* @param {Object} modelAttributes
* @param {Object} [options]
*
* @private
*/
insertQuery(table, valueHash, modelAttributes, options) {
options = options || {};
_.defaults(options, this.options);
......@@ -210,13 +235,18 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(replacements);
},
}
/*
Returns an insert into command for multiple values.
Parameters: table name + list of hashes of attribute-value-pairs.
@private
*/
/**
* Returns an insert into command for multiple values.
*
* @param {String} tableName
* @param {Object} fieldValueHashes
* @param {Object} options
* @param {Object} fieldMappedAttributes
*
* @private
*/
bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) {
options = options || {};
fieldMappedAttributes = fieldMappedAttributes || {};
......@@ -273,19 +303,20 @@ const QueryGenerator = {
};
return _.template(query, this._templateSettings)(replacements);
},
}
/*
Returns an update query.
Parameters:
- tableName -> Name of the table
- values -> A hash with attribute-value-pairs
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
@private
*/
/**
* Returns an update query
*
* @param {String} tableName
* @param {Object} attrValueHash
* @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer
OR a string with conditions (e.g. 'name="foo"'). If you use a string, you have to escape it on your own.
* @param {Object} options
* @param {Object} attributes
*
* @private
*/
updateQuery(tableName, attrValueHash, where, options, attributes) {
options = options || {};
_.defaults(options, this.options);
......@@ -380,20 +411,19 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(replacements).trim();
},
}
/*
Returns an update query.
Parameters:
- operator -> String with the arithmetic operator (e.g. '+' or '-')
- tableName -> Name of the table
- values -> A hash with attribute-value-pairs
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
@private
*/
/**
* Returns an update query using arithmetic operator
*
* @param {String} operator String with the arithmetic operator (e.g. '+' or '-')
* @param {String} tableName Name of the table
* @param {Object} attrValueHash A hash with attribute-value-pairs
* @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer
OR a string with conditions (e.g. 'name="foo"'). If you use a string, you have to escape it on your own.
* @param {Object} options
* @param {Object} attributes
*/
arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes) {
options = options || {};
_.defaults(options, { returning: true });
......@@ -432,7 +462,7 @@ const QueryGenerator = {
};
return _.template(query, this._templateSettings)(replacements);
},
}
nameIndexes(indexes, rawTablename) {
if (typeof rawTablename === 'object') {
......@@ -448,7 +478,7 @@ const QueryGenerator = {
return index;
});
},
}
/*
Returns an add index query.
......@@ -576,7 +606,7 @@ const QueryGenerator = {
);
return _.compact(ind).join(' ');
},
}
addConstraintQuery(tableName, options) {
options = options || {};
......@@ -589,7 +619,7 @@ const QueryGenerator = {
}
return `ALTER TABLE ${tableName} ADD ${constraintSnippet};`;
},
}
getConstraintSnippet(tableName, options) {
let constraintSnippet, constraintName;
......@@ -663,45 +693,11 @@ const QueryGenerator = {
default: throw new Error(`${options.type} is invalid.`);
}
return constraintSnippet;
},
}
removeConstraintQuery(tableName, constraintName) {
return `ALTER TABLE ${this.quoteIdentifiers(tableName)} DROP CONSTRAINT ${this.quoteIdentifiers(constraintName)}`;
},
quoteTable(param, as) {
let table = '';
if (as === true) {
as = param.as || param.name || param;
}
if (_.isObject(param)) {
if (this._dialect.supports.schemas) {
if (param.schema) {
table += this.quoteIdentifier(param.schema) + '.';
}
table += this.quoteIdentifier(param.tableName);
} else {
if (param.schema) {
table += param.schema + (param.delimiter || '.');
}
table += param.tableName;
table = this.quoteIdentifier(table);
}
} else {
table = this.quoteIdentifier(param);
}
if (as) {
table += ' AS ' + this.quoteIdentifier(as);
}
return table;
},
}
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers
......@@ -879,24 +875,83 @@ const QueryGenerator = {
} else {
throw new Error('Unknown structure passed to order / group: ' + util.inspect(collection));
}
},
}
/**
* Split a list of identifiers by "." and quote each part
*
* @param {String} dialect Dialect name
* @param {String} identifiers
* @param {Object} [options]
* @param {Boolean} [options.force=false]
* @param {Boolean} [options.quoteIdentifiers=true]
*
* @returns {String}
*/
quoteIdentifier(identifier, force) {
return QuoteHelper.quoteIdentifier(this.dialect, identifier, {
force,
quoteIdentifiers: this.options.quoteIdentifiers
});
}
/*
Split an identifier into .-separated tokens and quote each part
@private
*/
quoteIdentifiers(identifiers) {
if (identifiers.indexOf('.') !== -1) {
identifiers = identifiers.split('.');
return this.quoteIdentifier(identifiers.slice(0, identifiers.length - 1).join('.')) + '.' + this.quoteIdentifier(identifiers[identifiers.length - 1]);
const head = identifiers.slice(0, identifiers.length - 1).join('.');
const tail = identifiers[identifiers.length - 1];
return this.quoteIdentifier(head) + '.' + this.quoteIdentifier(tail);
}
return this.quoteIdentifier(identifiers);
}
/**
* Quote table name with optional alias and schema attribution
*
* @param {String|Object} param table string or object
* @param {String|Boolean} alias alias name
*
* @returns {String}
*/
quoteTable(param, alias) {
let table = '';
if (alias === true) {
alias = param.as || param.name || param;
}
if (_.isObject(param)) {
if (this._dialect.supports.schemas) {
if (param.schema) {
table += this.quoteIdentifier(param.schema) + '.';
}
table += this.quoteIdentifier(param.tableName);
} else {
if (param.schema) {
table += param.schema + (param.delimiter || '.');
}
table += param.tableName;
table = this.quoteIdentifier(table);
}
} else {
return this.quoteIdentifier(identifiers);
table = this.quoteIdentifier(param);
}
},
if (alias) {
table += ' AS ' + this.quoteIdentifier(alias);
}
return table;
}
/*
Escape a value (e.g. a string, number or date)
@private
@private
*/
escape(value, field, options) {
options = options || {};
......@@ -932,7 +987,62 @@ const QueryGenerator = {
}
return SqlString.escape(value, this.options.timezone, this.dialect);
},
}
isIdentifierQuoted(identifier) {
return QuoteHelper.isIdentifierQuoted(identifier);
}
/**
* 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) {
let paths = _.toPath(path);
let pathStr;
let quotedColumn;
switch (this.dialect) {
case 'mysql':
/**
* Sub paths need to be quoted as ECMAScript identifiers
* https://bugs.mysql.com/bug.php?id=81896
*/
paths = paths.map(subPath => Utils.addTicks(subPath, '"'));
pathStr = `${['$'].concat(paths).join('.')}`;
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `(${quotedColumn}->>'${pathStr}')`;
case 'sqlite':
pathStr = this.escape(['$']
.concat(paths)
.join('.')
.replace(/\.(\d+)(?:(?=\.)|$)/g, (_, digit) => `[${digit}]`));
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `json_extract(${quotedColumn}, ${pathStr})`;
case 'postgres':
pathStr = this.escape(`{${paths.join(',')}}`);
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `(${quotedColumn}#>>${pathStr})`;
default:
throw new Error(`Unsupported ${this.dialect} for JSON operations`);
}
}
/*
Returns a query for selecting elements in the table <tableName>.
......@@ -1220,7 +1330,7 @@ const QueryGenerator = {
}
return `${query};`;
},
}
escapeAttributes(attributes, options, mainTableAs) {
return attributes && attributes.map(attr => {
......@@ -1255,7 +1365,7 @@ const QueryGenerator = {
return attr;
});
},
}
generateInclude(include, parentTableName, topLevelInfo) {
const joinQueries = {
......@@ -1400,7 +1510,7 @@ const QueryGenerator = {
subQuery: joinQueries.subQuery.join(''),
attributes
};
},
}
generateJoin(include, topLevelInfo) {
const association = include.association;
......@@ -1479,7 +1589,7 @@ const QueryGenerator = {
subQuery: []
}
};
},
}
generateThroughJoin(include, includeAs, parentTableName, topLevelInfo) {
const through = include.through;
......@@ -1582,7 +1692,7 @@ const QueryGenerator = {
condition: joinCondition,
attributes
};
},
}
/*
* Generates subQueryFilter - a select nested in the where clause of the subQuery.
......@@ -1683,7 +1793,7 @@ const QueryGenerator = {
')',
'IS NOT NULL'
].join(' '));
},
}
/*
* For a given include hierarchy creates a copy of it where only the required includes
......@@ -1699,7 +1809,7 @@ const QueryGenerator = {
}
return copy;
},
}
getQueryOrders(options, model, subQuery) {
const mainQueryOrder = [];
......@@ -1746,7 +1856,7 @@ const QueryGenerator = {
}
return {mainQueryOrder, subQueryOrder};
},
}
selectFromTableFragment(options, model, attributes, tables, mainTableAs) {
let fragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables;
......@@ -1756,111 +1866,7 @@ const QueryGenerator = {
}
return fragment;
},
/**
* Returns a query that starts a transaction.
*
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
setAutocommitQuery(value, options) {
if (options.parent) {
return;
}
// no query when value is not explicitly set
if (typeof value === 'undefined' || value === null) {
return;
}
return 'SET autocommit = ' + (value ? 1 : 0) + ';';
},
/**
* Returns a query that sets the transaction isolation level.
*
* @param {String} value The isolation level.
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
setIsolationLevelQuery(value, options) {
if (options.parent) {
return;
}
return 'SET SESSION TRANSACTION ISOLATION LEVEL ' + value + ';';
},
generateTransactionId() {
return uuid.v4();
},
/**
* Returns a query that starts a transaction.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
startTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
}
return 'START TRANSACTION;';
},
/**
* Returns a query that defers the constraints. Only works for postgres.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
deferConstraintsQuery() {},
setConstraintQuery() {},
setDeferredQuery() {},
setImmediateQuery() {},
/**
* Returns a query that commits a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
commitTransactionQuery(transaction) {
if (transaction.parent) {
return;
}
return 'COMMIT;';
},
/**
* Returns a query that rollbacks a transaction.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
rollbackTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
}
return 'ROLLBACK;';
},
}
/**
* Returns an SQL fragment for adding result constraints
......@@ -1886,7 +1892,7 @@ const QueryGenerator = {
/* eslint-enable */
return fragment;
},
}
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
let result;
......@@ -1958,7 +1964,7 @@ const QueryGenerator = {
}
return result;
},
}
whereQuery(where, options) {
const query = this.whereItemsQuery(where, options);
......@@ -1966,7 +1972,7 @@ const QueryGenerator = {
return 'WHERE '+query;
}
return '';
},
}
whereItemsQuery(where, options, binding) {
if (
......@@ -1997,55 +2003,7 @@ const QueryGenerator = {
}
return items.length && items.filter(item => item && item.length).join(binding) || '';
},
OperatorMap: {
[Op.eq]: '=',
[Op.ne]: '!=',
[Op.gte]: '>=',
[Op.gt]: '>',
[Op.lte]: '<=',
[Op.lt]: '<',
[Op.not]: 'IS NOT',
[Op.is]: 'IS',
[Op.in]: 'IN',
[Op.notIn]: 'NOT IN',
[Op.like]: 'LIKE',
[Op.notLike]: 'NOT LIKE',
[Op.iLike]: 'ILIKE',
[Op.notILike]: 'NOT ILIKE',
[Op.regexp]: '~',
[Op.notRegexp]: '!~',
[Op.iRegexp]: '~*',
[Op.notIRegexp]: '!~*',
[Op.between]: 'BETWEEN',
[Op.notBetween]: 'NOT BETWEEN',
[Op.overlap]: '&&',
[Op.contains]: '@>',
[Op.contained]: '<@',
[Op.adjacent]: '-|-',
[Op.strictLeft]: '<<',
[Op.strictRight]: '>>',
[Op.noExtendRight]: '&<',
[Op.noExtendLeft]: '&>',
[Op.any]: 'ANY',
[Op.all]: 'ALL',
[Op.and]: ' AND ',
[Op.or]: ' OR ',
[Op.col]: 'COL',
[Op.placeholder]: '$$PLACEHOLDER$$',
[Op.raw]: 'DEPRECATED' //kept here since we still throw an explicit error if operator being used remove by v5,
},
OperatorsAliasMap: {},
setOperatorsAliases(aliases) {
if (!aliases || _.isEmpty(aliases)) {
this.OperatorsAliasMap = false;
} else {
this.OperatorsAliasMap = _.assign({}, aliases);
}
},
}
whereItemQuery(key, value, options) {
options = options || {};
......@@ -2138,7 +2096,7 @@ const QueryGenerator = {
}
return this._joinKeyValue(key, this.escape(value, field), this.OperatorMap[Op.eq], options.prefix);
},
}
_findField(key, options) {
if (options.field) {
......@@ -2152,32 +2110,7 @@ const QueryGenerator = {
if (options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]) {
return options.model.fieldRawAttributesMap[key];
}
},
_replaceAliases(orig) {
const obj = {};
if (!this.OperatorsAliasMap) {
return orig;
}
Utils.getOperators(orig).forEach(op => {
const item = orig[op];
if (_.isPlainObject(item)) {
obj[op] = this._replaceAliases(item);
} else {
obj[op] = item;
}
});
_.forOwn(orig, (item, prop) => {
prop = this.OperatorsAliasMap[prop] || prop;
if (_.isPlainObject(item)) {
item = this._replaceAliases(item);
}
obj[prop] = item;
});
return obj;
},
}
// OR/AND/NOT grouping logic
_whereGroupBind(key, value, options) {
......@@ -2204,7 +2137,7 @@ const QueryGenerator = {
}
return value ? outerBinding + '('+value+')' : undefined;
},
}
_whereBind(binding, key, value, options) {
if (_.isPlainObject(value)) {
......@@ -2219,7 +2152,7 @@ const QueryGenerator = {
value = value.filter(item => item && item.length);
return value.length ? '('+value.join(binding)+')' : undefined;
},
}
_whereJSON(key, value, options) {
const items = [];
......@@ -2244,8 +2177,7 @@ const QueryGenerator = {
const result = items.join(this.OperatorMap[Op.and]);
return items.length > 1 ? '('+result+')' : result;
},
}
_traverseJSON(items, baseKey, prop, item, path) {
let cast;
......@@ -2272,11 +2204,11 @@ const QueryGenerator = {
item = this._toJSONValue(item);
items.push(this.whereItemQuery(this._castKey(pathKey, item, cast), {[Op.eq]: item}));
},
}
_toJSONValue(value) {
return value;
},
}
_castKey(key, value, cast, json) {
cast = cast || this._getJsonCast(Array.isArray(value) ? value[0] : value);
......@@ -2285,7 +2217,7 @@ const QueryGenerator = {
}
return new Utils.Literal(key);
},
}
_getJsonCast(value) {
if (typeof value === 'number') {
......@@ -2298,7 +2230,7 @@ const QueryGenerator = {
return 'boolean';
}
return;
},
}
_joinKeyValue(key, value, comparator, prefix) {
if (!key) {
......@@ -2309,7 +2241,7 @@ const QueryGenerator = {
}
key = this._getSafeKey(key, prefix);
return [key, value].join(' '+comparator+' ');
},
}
_getSafeKey(key, prefix) {
if (key instanceof Utils.SequelizeMethod) {
......@@ -2332,7 +2264,7 @@ const QueryGenerator = {
}
return this._prefixKey(this.quoteIdentifier(key), prefix);
},
}
_prefixKey(key, prefix) {
if (prefix) {
......@@ -2344,7 +2276,7 @@ const QueryGenerator = {
}
return key;
},
}
_whereParseSingleValueObject(key, field, prop, value, options) {
if (prop === Op.not) {
......@@ -2430,7 +2362,7 @@ const QueryGenerator = {
}
return this._joinKeyValue(key, this.escape(value, field, escapeOptions), comparator, options.prefix);
},
}
/*
Takes something and transforms it into values of a where condition.
......@@ -2499,7 +2431,7 @@ const QueryGenerator = {
}
return result ? result : '1=1';
},
}
// A recursive parser for nested where conditions
parseConditionObject(conditions, path) {
......@@ -2512,15 +2444,14 @@ const QueryGenerator = {
}
return result;
}, []);
},
isIdentifierQuoted(string) {
return /^\s*(?:([`"'])(?:(?!\1).|\1{2})*\1\.?)+\s*$/i.test(string);
},
}
booleanValue(value) {
return value;
}
};
}
_.assignIn(QueryGenerator.prototype, require('./query-generator/operators'));
_.assignIn(QueryGenerator.prototype, require('./query-generator/transaction'));
module.exports = QueryGenerator;
/**
* Quote helpers implement quote ability for all dialects.
* These are basic block of query building
*
* Its better to implement all dialect implementation together here. Which will allow
* even abstract generator to use them by just specifying dialect type.
*
* Defining these helpers in each query dialect will leave
* code in dual dependency of abstract <-> specific dialect
*/
'use strict';
const Utils = require('../../../../utils');
/**
*
* @param {String} dialect Dialect name
* @param {String} identifier Identifier to quote
* @param {Object} [options]
* @param {Boolean} [options.force=false]
* @param {Boolean} [options.quoteIdentifiers=true]
*
* @returns {String}
*/
function quoteIdentifier(dialect, identifier, options) {
if (identifier === '*') return identifier;
options = Utils.defaults(options || {}, {
force: false,
quoteIdentifiers: true
});
switch (dialect) {
case 'sqlite':
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
case 'mysql':
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
case 'postgres':
if (
options.force !== true &&
options.quoteIdentifiers === false &&
identifier.indexOf('.') === -1 &&
identifier.indexOf('->') === -1
) {
// In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase
// characters, they must always be double-quoted. This makes it
// impossible to write queries in portable SQL if tables are created in
// this way. Hence, we strip quotes if we don't want case sensitivity.
return Utils.removeTicks(identifier, '"');
} else {
return Utils.addTicks(Utils.removeTicks(identifier, '"'), '"');
}
case 'mssql':
return '[' + identifier.replace(/[\[\]']+/g, '') + ']';
default:
throw new Error(`Dialect "${dialect}" is not supported`);
}
}
module.exports.quoteIdentifier = quoteIdentifier;
/**
* Test if a give string is already quoted
*
* @param {String} identifier
*
* @return Boolean
*/
function isIdentifierQuoted(identifier) {
return /^\s*(?:([`"'])(?:(?!\1).|\1{2})*\1\.?)+\s*$/i.test(identifier);
}
module.exports.isIdentifierQuoted = isIdentifierQuoted;
\ No newline at end of file
'use strict';
const _ = require('lodash');
const Op = require('../../../operators');
const Utils = require('../../../utils');
const OperatorHelpers = {
OperatorMap: {
[Op.eq]: '=',
[Op.ne]: '!=',
[Op.gte]: '>=',
[Op.gt]: '>',
[Op.lte]: '<=',
[Op.lt]: '<',
[Op.not]: 'IS NOT',
[Op.is]: 'IS',
[Op.in]: 'IN',
[Op.notIn]: 'NOT IN',
[Op.like]: 'LIKE',
[Op.notLike]: 'NOT LIKE',
[Op.iLike]: 'ILIKE',
[Op.notILike]: 'NOT ILIKE',
[Op.regexp]: '~',
[Op.notRegexp]: '!~',
[Op.iRegexp]: '~*',
[Op.notIRegexp]: '!~*',
[Op.between]: 'BETWEEN',
[Op.notBetween]: 'NOT BETWEEN',
[Op.overlap]: '&&',
[Op.contains]: '@>',
[Op.contained]: '<@',
[Op.adjacent]: '-|-',
[Op.strictLeft]: '<<',
[Op.strictRight]: '>>',
[Op.noExtendRight]: '&<',
[Op.noExtendLeft]: '&>',
[Op.any]: 'ANY',
[Op.all]: 'ALL',
[Op.and]: ' AND ',
[Op.or]: ' OR ',
[Op.col]: 'COL',
[Op.placeholder]: '$$PLACEHOLDER$$'
},
OperatorsAliasMap: {},
setOperatorsAliases(aliases) {
if (!aliases || _.isEmpty(aliases)) {
this.OperatorsAliasMap = false;
} else {
this.OperatorsAliasMap = _.assign({}, aliases);
}
},
_replaceAliases(orig) {
const obj = {};
if (!this.OperatorsAliasMap) {
return orig;
}
Utils.getOperators(orig).forEach(op => {
const item = orig[op];
if (_.isPlainObject(item)) {
obj[op] = this._replaceAliases(item);
} else {
obj[op] = item;
}
});
_.forOwn(orig, (item, prop) => {
prop = this.OperatorsAliasMap[prop] || prop;
if (_.isPlainObject(item)) {
item = this._replaceAliases(item);
}
obj[prop] = item;
});
return obj;
}
};
module.exports = OperatorHelpers;
\ No newline at end of file
'use strict';
const uuid = require('uuid');
const TransactionQueries = {
/**
* Returns a query that starts a transaction.
*
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
setAutocommitQuery(value, options) {
if (options.parent) {
return;
}
// no query when value is not explicitly set
if (typeof value === 'undefined' || value === null) {
return;
}
return `SET autocommit = ${(value ? 1 : 0)};`;
},
/**
* Returns a query that sets the transaction isolation level.
*
* @param {String} value The isolation level.
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
setIsolationLevelQuery(value, options) {
if (options.parent) {
return;
}
return `SET SESSION TRANSACTION ISOLATION LEVEL ${value};`;
},
generateTransactionId() {
return uuid.v4();
},
/**
* Returns a query that starts a transaction.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
startTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return `SAVEPOINT ${this.quoteIdentifier(transaction.name, true)};`;
}
return 'START TRANSACTION;';
},
/**
* Returns a query that defers the constraints. Only works for postgres.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
deferConstraintsQuery() {},
setConstraintQuery() {},
setDeferredQuery() {},
setImmediateQuery() {},
/**
* Returns a query that commits a transaction.
*
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
commitTransactionQuery(transaction) {
if (transaction.parent) {
return;
}
return 'COMMIT;';
},
/**
* Returns a query that rollbacks a transaction.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
* @private
*/
rollbackTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return `ROLLBACK TO SAVEPOINT ${this.quoteIdentifier(transaction.name, true)};`;
}
return 'ROLLBACK;';
}
};
module.exports = TransactionQueries;
\ No newline at end of file
......@@ -12,8 +12,7 @@ class MssqlDialect extends AbstractDialect {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
this.QueryGenerator = new QueryGenerator({
_dialect: this,
sequelize
});
......
......@@ -7,7 +7,6 @@ const TableHints = require('../../table-hints');
const AbstractQueryGenerator = require('../abstract/query-generator');
const randomBytes = require('crypto').randomBytes;
const semver = require('semver');
const Op = require('../../operators');
/* istanbul ignore next */
......@@ -15,11 +14,7 @@ const throwMethodUndefined = function(methodName) {
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.');
};
const QueryGenerator = {
__proto__: AbstractQueryGenerator,
options: {},
dialect: 'mssql',
class MSSQLQueryGenerator extends AbstractQueryGenerator {
createSchema(schema) {
return [
'IF NOT EXISTS (SELECT schema_name',
......@@ -31,7 +26,7 @@ const QueryGenerator = {
";'",
'END;'
].join(' ');
},
}
dropSchema(schema) {
// Mimics Postgres CASCADE, will drop objects belonging to the schema
......@@ -66,7 +61,7 @@ const QueryGenerator = {
"EXEC sp_executesql N'DROP SCHEMA", this.quoteIdentifier(schema), ";'",
'END;'
].join(' ');
},
}
showSchemasQuery() {
return [
......@@ -75,7 +70,7 @@ const QueryGenerator = {
"'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
].join(' ');
},
}
versionQuery() {
// Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch
......@@ -84,7 +79,7 @@ const QueryGenerator = {
"SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion')));",
"SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'"
].join(' ');
},
}
createTableQuery(tableName, attributes, options) {
const query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)",
......@@ -147,7 +142,7 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(values).trim() + ';';
},
}
describeTableQuery(tableName, schema) {
let sql = [
......@@ -181,7 +176,7 @@ const QueryGenerator = {
}
return sql;
},
}
renameTableQuery(before, after) {
const query = 'EXEC sp_rename <%= before %>, <%= after %>;';
......@@ -189,11 +184,11 @@ const QueryGenerator = {
before: this.quoteTable(before),
after: this.quoteTable(after)
});
},
}
showTablesQuery() {
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;';
},
}
dropTableQuery(tableName) {
const query = "IF OBJECT_ID('<%= table %>', 'U') IS NOT NULL DROP TABLE <%= table %>";
......@@ -202,7 +197,7 @@ const QueryGenerator = {
};
return _.template(query, this._templateSettings)(values).trim() + ';';
},
}
addColumnQuery(table, key, dataType) {
// FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
......@@ -221,7 +216,7 @@ const QueryGenerator = {
table: this.quoteTable(table),
attribute
});
},
}
removeColumnQuery(tableName, attributeName) {
const query = 'ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;';
......@@ -229,7 +224,7 @@ const QueryGenerator = {
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
}
changeColumnQuery(tableName, attributes) {
const query = 'ALTER TABLE <%= tableName %> <%= query %>;';
......@@ -265,7 +260,7 @@ const QueryGenerator = {
tableName: this.quoteTable(tableName),
query: finalQuery
});
},
}
renameColumnQuery(tableName, attrBefore, attributes) {
const query = "EXEC sp_rename '<%= tableName %>.<%= before %>', '<%= after %>', 'COLUMN';",
......@@ -276,7 +271,7 @@ const QueryGenerator = {
before: attrBefore,
after: newName
});
},
}
bulkInsertQuery(tableName, attrValueHashes, options, attributes) {
options = options || {};
......@@ -352,7 +347,7 @@ const QueryGenerator = {
offset += batch;
}
return commands.join(';');
},
}
updateQuery(tableName, attrValueHash, where, options, attributes) {
let sql = super.updateQuery(tableName, attrValueHash, where, options, attributes);
......@@ -361,7 +356,7 @@ const QueryGenerator = {
sql = sql.replace('UPDATE', updateArgs);
}
return sql;
},
}
upsertQuery(tableName, insertValues, updateValues, where, model) {
const targetTableAlias = this.quoteTable(`${tableName}_target`);
......@@ -478,7 +473,7 @@ const QueryGenerator = {
query = `SET IDENTITY_INSERT ${tableNameQuoted} ON; ${query} SET IDENTITY_INSERT ${tableNameQuoted} OFF;`;
}
return query;
},
}
deleteQuery(tableName, where, options) {
options = options || {};
......@@ -513,18 +508,18 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(replacements);
},
}
showIndexesQuery(tableName) {
const sql = "EXEC sys.sp_helpindex @objname = N'<%= tableName %>';";
return _.template(sql, this._templateSettings)({
tableName: this.quoteTable(tableName)
});
},
}
showConstraintsQuery(tableName) {
return `EXEC sp_helpconstraint @objname = ${this.escape(this.quoteTable(tableName))};`;
},
}
removeIndexQuery(tableName, indexNameOrAttributes) {
const sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>';
......@@ -540,7 +535,7 @@ const QueryGenerator = {
};
return _.template(sql, this._templateSettings)(values);
},
}
attributeToSQL(attribute) {
if (!_.isPlainObject(attribute)) {
......@@ -618,7 +613,7 @@ const QueryGenerator = {
}
return template;
},
}
attributesToSQL(attributes, options) {
const result = {},
......@@ -651,36 +646,31 @@ const QueryGenerator = {
}
return result;
},
}
createTrigger() {
throwMethodUndefined('createTrigger');
},
}
dropTrigger() {
throwMethodUndefined('dropTrigger');
},
}
renameTrigger() {
throwMethodUndefined('renameTrigger');
},
}
createFunction() {
throwMethodUndefined('createFunction');
},
}
dropFunction() {
throwMethodUndefined('dropFunction');
},
}
renameFunction() {
throwMethodUndefined('renameFunction');
},
quoteIdentifier(identifier) {
if (identifier === '*') return identifier;
return '[' + identifier.replace(/[\[\]']+/g, '') + ']';
},
}
/**
* Generate common SQL prefix for ForeignKeysQuery.
......@@ -706,7 +696,7 @@ const QueryGenerator = {
'INNER JOIN SYS.COLUMNS COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID ' +
'INNER JOIN SYS.TABLES RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID ' +
'INNER JOIN SYS.COLUMNS RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID';
},
}
/**
* Generates an SQL query that returns all foreign keys details of a table.
......@@ -723,7 +713,7 @@ const QueryGenerator = {
sql += ' AND SCHEMA_NAME(TB.SCHEMA_ID) =' + wrapSingleQuote(table.schema);
}
return sql;
},
}
getForeignKeyQuery(table, attributeName) {
const tableName = table.tableName || table;
......@@ -736,7 +726,7 @@ const QueryGenerator = {
}
return sql;
},
}
getPrimaryKeyConstraintQuery(table, attributeName) {
const tableName = wrapSingleQuote(table.tableName || table);
......@@ -754,14 +744,14 @@ const QueryGenerator = {
`AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`,
`AND K.TABLE_NAME = ${tableName};`
].join(' ');
},
}
dropForeignKeyQuery(tableName, foreignKey) {
return _.template('ALTER TABLE <%= table %> DROP <%= key %>', this._templateSettings)({
table: this.quoteTable(tableName),
key: this.quoteIdentifier(foreignKey)
});
},
}
getDefaultConstraintQuery(tableName, attributeName) {
const sql = 'SELECT name FROM SYS.DEFAULT_CONSTRAINTS ' +
......@@ -772,7 +762,7 @@ const QueryGenerator = {
table: this.quoteTable(tableName),
column: attributeName
});
},
}
dropConstraintQuery(tableName, constraintName) {
const sql = 'ALTER TABLE <%= table %> DROP CONSTRAINT <%= constraint %>;';
......@@ -780,19 +770,19 @@ const QueryGenerator = {
table: this.quoteTable(tableName),
constraint: this.quoteIdentifier(constraintName)
});
},
}
setAutocommitQuery() {
return '';
},
}
setIsolationLevelQuery() {
},
}
generateTransactionId() {
return randomBytes(10).toString('hex');
},
}
startTransactionQuery(transaction) {
if (transaction.parent) {
......@@ -800,7 +790,7 @@ const QueryGenerator = {
}
return 'BEGIN TRANSACTION;';
},
}
commitTransactionQuery(transaction) {
if (transaction.parent) {
......@@ -808,7 +798,7 @@ const QueryGenerator = {
}
return 'COMMIT TRANSACTION;';
},
}
rollbackTransactionQuery(transaction) {
if (transaction.parent) {
......@@ -816,7 +806,7 @@ const QueryGenerator = {
}
return 'ROLLBACK TRANSACTION;';
},
}
selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) {
let topFragment = '';
......@@ -874,7 +864,7 @@ const QueryGenerator = {
}
return mainFragment;
},
}
addLimitAndOffset(options, model) {
// Skip handling of limit and offset as postfixes for older SQL Server versions
......@@ -907,7 +897,7 @@ const QueryGenerator = {
}
return fragment;
},
}
booleanValue(value) {
return value ? 1 : 0;
......@@ -919,4 +909,4 @@ function wrapSingleQuote(identifier) {
return Utils.addTicks(Utils.removeTicks(identifier, "'"), "'");
}
module.exports = QueryGenerator;
module.exports = MSSQLQueryGenerator;
\ No newline at end of file
......@@ -12,8 +12,7 @@ class MysqlDialect extends AbstractDialect {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
this.QueryGenerator = new QueryGenerator({
_dialect: this,
sequelize
});
......
......@@ -6,26 +6,27 @@ const AbstractQueryGenerator = require('../abstract/query-generator');
const util = require('util');
const Op = require('../../operators');
const QueryGenerator = {
__proto__: AbstractQueryGenerator,
dialect: 'mysql',
class MySQLQueryGenerator extends AbstractQueryGenerator {
constructor(options) {
super(options);
OperatorMap: Object.assign({}, AbstractQueryGenerator.OperatorMap, {
[Op.regexp]: 'REGEXP',
[Op.notRegexp]: 'NOT REGEXP'
}),
this.OperatorMap = Object.assign({}, this.OperatorMap, {
[Op.regexp]: 'REGEXP',
[Op.notRegexp]: 'NOT REGEXP'
});
}
createSchema() {
return 'SHOW TABLES';
},
}
showSchemasQuery() {
return 'SHOW TABLES';
},
}
versionQuery() {
return 'SELECT VERSION() as `version`';
},
}
createTableQuery(tableName, attributes, options) {
options = _.extend({
......@@ -100,11 +101,11 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(values).trim() + ';';
},
}
showTablesQuery() {
return 'SHOW TABLES;';
},
}
addColumnQuery(table, key, dataType) {
const definition = this.attributeToSQL(dataType, {
......@@ -114,11 +115,11 @@ const QueryGenerator = {
});
return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition};`;
},
}
removeColumnQuery(tableName, attributeName) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(attributeName)};`;
},
}
changeColumnQuery(tableName, attributes) {
const attrString = [];
......@@ -146,7 +147,7 @@ const QueryGenerator = {
}
return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`;
},
}
renameColumnQuery(tableName, attrBefore, attributes) {
const attrString = [];
......@@ -157,7 +158,7 @@ const QueryGenerator = {
}
return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`;
},
}
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
......@@ -218,7 +219,7 @@ const QueryGenerator = {
}
return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
},
}
_toJSONValue(value) {
// true/false are stored as strings in mysql
......@@ -230,7 +231,7 @@ const QueryGenerator = {
return 'null';
}
return value;
},
}
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
options.onDuplicate = 'UPDATE ';
......@@ -241,7 +242,7 @@ const QueryGenerator = {
}).join(', ');
return this.insertQuery(tableName, insertValues, model.rawAttributes, options);
},
}
deleteQuery(tableName, where, options, model) {
options = options || {};
......@@ -268,11 +269,11 @@ const QueryGenerator = {
query += limit;
return query;
},
}
showIndexesQuery(tableName, options) {
return 'SHOW INDEX FROM ' + this.quoteTable(tableName) + ((options || {}).database ? ' FROM `' + options.database + '`' : '');
},
}
showConstraintsQuery(table, constraintName) {
const tableName = table.tableName || table;
......@@ -298,7 +299,7 @@ const QueryGenerator = {
}
return sql + ';';
},
}
removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes;
......@@ -308,7 +309,7 @@ const QueryGenerator = {
}
return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`;
},
}
attributeToSQL(attribute, options) {
if (!_.isPlainObject(attribute)) {
......@@ -375,7 +376,7 @@ const QueryGenerator = {
}
return template;
},
}
attributesToSQL(attributes, options) {
const result = {};
......@@ -386,12 +387,7 @@ const QueryGenerator = {
}
return result;
},
quoteIdentifier(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
},
}
/**
* Check whether the statmement is json function or simple path
......@@ -458,27 +454,7 @@ const QueryGenerator = {
// 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) {
/**
* Sub paths need to be quoted as ECMAScript identifiers
*
* https://bugs.mysql.com/bug.php?id=81896
*/
const paths = _.toPath(path).map(subPath => Utils.addTicks(subPath, '"'));
const pathStr = `${['$'].concat(paths).join('.')}`;
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `(${quotedColumn}->>'${pathStr}')`;
},
}
/**
* Generates fields for getForeignKeysQuery
......@@ -500,7 +476,7 @@ const QueryGenerator = {
'REFERENCED_TABLE_NAME as referencedTableName',
'REFERENCED_COLUMN_NAME as referencedColumnName',
].join(',');
},
}
/**
* Generates an SQL query that returns all foreign keys of a table.
......@@ -513,7 +489,7 @@ const QueryGenerator = {
getForeignKeysQuery(tableName, schemaName) {
return 'SELECT ' + this._getForeignKeysQueryFields() + ' FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = \'' + tableName + /* jshint ignore: line */
'\' AND CONSTRAINT_NAME!=\'PRIMARY\' AND CONSTRAINT_SCHEMA=\'' + schemaName + '\' AND REFERENCED_TABLE_NAME IS NOT NULL;'; /* jshint ignore: line */
},
}
/**
* Generates an SQL query that returns the foreign key constraint of a given column.
......@@ -537,7 +513,7 @@ const QueryGenerator = {
+ ' AND COLUMN_NAME = ' + wrapSingleQuote(columnName)
+ ' AND REFERENCED_TABLE_NAME IS NOT NULL'
+ ')';
},
}
/**
* Generates an SQL query that removes a foreign key from a table.
......@@ -557,4 +533,4 @@ function wrapSingleQuote(identifier) {
return Utils.addTicks(identifier, '\'');
}
module.exports = QueryGenerator;
module.exports = MySQLQueryGenerator;
......@@ -12,8 +12,7 @@ class PostgresDialect extends AbstractDialect {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
this.QueryGenerator = new QueryGenerator({
_dialect: this,
sequelize
});
......
......@@ -7,14 +7,10 @@ const AbstractQueryGenerator = require('../abstract/query-generator');
const semver = require('semver');
const _ = require('lodash');
const QueryGenerator = {
__proto__: AbstractQueryGenerator,
options: {},
dialect: 'postgres',
class PostgresQueryGenerator extends AbstractQueryGenerator {
setSearchPath(searchPath) {
return `SET search_path to ${searchPath};`;
},
}
createSchema(schema) {
const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0);
......@@ -24,19 +20,19 @@ const QueryGenerator = {
}
return `CREATE SCHEMA ${schema};`;
},
}
dropSchema(schema) {
return `DROP SCHEMA IF EXISTS ${schema} CASCADE;`;
},
}
showSchemasQuery() {
return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';";
},
}
versionQuery() {
return 'SHOW SERVER_VERSION';
},
}
createTableQuery(tableName, attributes, options) {
options = _.extend({}, options || {});
......@@ -88,16 +84,16 @@ const QueryGenerator = {
}
return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${values.table} (${values.attributes})${values.comments};`;
},
}
dropTableQuery(tableName, options) {
options = options || {};
return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`;
},
}
showTablesQuery() {
return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type LIKE '%TABLE' AND table_name != 'spatial_ref_sys';";
},
}
describeTableQuery(tableName, schema) {
if (!schema) {
......@@ -121,7 +117,7 @@ const QueryGenerator = {
'AND pk.table_name=c.table_name ' +
'AND pk.column_name=c.column_name ' +
`WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `;
},
}
/**
* Check whether the statmement is json function or simple path
......@@ -188,22 +184,7 @@ const QueryGenerator = {
// 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 = this.escape(`{${paths.join(',')}}`);
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `(${quotedColumn}#>>${pathStr})`;
},
}
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
......@@ -234,8 +215,8 @@ const QueryGenerator = {
return str;
}
}
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
},
return super.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
}
addColumnQuery(table, key, dataType) {
......@@ -251,13 +232,13 @@ const QueryGenerator = {
}
return query;
},
}
removeColumnQuery(tableName, attributeName) {
const quotedTableName = this.quoteTable(this.extractTableDetails(tableName));
const quotedAttributeName = this.quoteIdentifier(attributeName);
return `ALTER TABLE ${quotedTableName} DROP COLUMN ${quotedAttributeName};`;
},
}
changeColumnQuery(tableName, attributes) {
const query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;';
......@@ -327,7 +308,7 @@ const QueryGenerator = {
}
return sql.join('');
},
}
renameColumnQuery(tableName, attrBefore, attributes) {
......@@ -341,7 +322,7 @@ const QueryGenerator = {
}
return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`;
},
}
fn(fnName, tableName, parameters, body, returns, language) {
fnName = fnName || 'testfunc';
......@@ -350,7 +331,7 @@ const QueryGenerator = {
parameters = parameters || '';
return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`;
},
}
exceptionFn(fnName, tableName, parameters, main, then, when, returns, language) {
when = when || 'unique_violation';
......@@ -358,7 +339,7 @@ const QueryGenerator = {
const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`;
return this.fn(fnName, tableName, parameters, body, returns, language);
},
}
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
const primaryField = this.quoteIdentifier(model.primaryKeyField);
......@@ -376,7 +357,7 @@ const QueryGenerator = {
`${insert} created := true;`,
`${update}; created := false`
);
},
}
deleteQuery(tableName, where, options, model) {
let query;
......@@ -429,7 +410,7 @@ const QueryGenerator = {
}
return _.template(query, this._templateSettings)(replacements);
},
}
showIndexesQuery(tableName) {
let schemaJoin = '';
......@@ -447,7 +428,7 @@ const QueryGenerator = {
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' +
`t.relkind = 'r' and t.relname = '${tableName}'${schemaWhere} ` +
'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;';
},
}
showConstraintsQuery(tableName) {
//Postgres converts camelCased alias to lowercase unless quoted
......@@ -464,7 +445,7 @@ const QueryGenerator = {
'from INFORMATION_SCHEMA.table_constraints',
`WHERE table_name='${tableName}';`
].join(' ');
},
}
removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes;
......@@ -474,7 +455,7 @@ const QueryGenerator = {
}
return `DROP INDEX IF EXISTS ${this.quoteIdentifiers(indexName)}`;
},
}
addLimitAndOffset(options) {
let fragment = '';
......@@ -488,7 +469,7 @@ const QueryGenerator = {
/* eslint-enable */
return fragment;
},
}
attributeToSQL(attribute) {
if (!_.isPlainObject(attribute)) {
......@@ -573,11 +554,11 @@ const QueryGenerator = {
}
return sql;
},
}
deferConstraintsQuery(options) {
return options.deferrable.toString(this);
},
}
setConstraintQuery(columns, type) {
let columnFragment = 'ALL';
......@@ -587,15 +568,15 @@ const QueryGenerator = {
}
return 'SET CONSTRAINTS ' + columnFragment + ' ' + type;
},
}
setDeferredQuery(columns) {
return this.setConstraintQuery(columns, 'DEFERRED');
},
}
setImmediateQuery(columns) {
return this.setConstraintQuery(columns, 'IMMEDIATE');
},
}
attributesToSQL(attributes, options) {
const result = {};
......@@ -606,7 +587,7 @@ const QueryGenerator = {
}
return result;
},
}
createTrigger(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
......@@ -620,15 +601,15 @@ const QueryGenerator = {
+ `\tON ${tableName}\n`
+ `\t${expandedOptions}\n`
+ `\tEXECUTE PROCEDURE ${functionName}(${paramList});`;
},
}
dropTrigger(tableName, triggerName) {
return `DROP TRIGGER ${triggerName} ON ${tableName} RESTRICT;`;
},
}
renameTrigger(tableName, oldTriggerName, newTriggerName) {
return `ALTER TRIGGER ${oldTriggerName} ON ${tableName} RENAME TO ${newTriggerName};`;
},
}
createFunction(functionName, params, returnType, language, body, options) {
if (!functionName || !returnType || !language || !body) throw new Error('createFunction missing some parameters. Did you pass functionName, returnType, language and body?');
......@@ -643,19 +624,19 @@ const QueryGenerator = {
+ `\t${indentedBody}\n`
+ 'END;\n'
+ `$func$ language '${language}'${expandedOptions};`;
},
}
dropFunction(functionName, params) {
if (!functionName) throw new Error('requires functionName');
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
const paramList = this.expandFunctionParamList(params);
return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`;
},
}
renameFunction(oldFunctionName, params, newFunctionName) {
const paramList = this.expandFunctionParamList(params);
return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`;
},
}
databaseConnectionUri(config) {
let uri = config.protocol + '://' + config.user + ':' + config.password + '@' + config.host;
......@@ -667,11 +648,11 @@ const QueryGenerator = {
uri += '?ssl=' + config.ssl;
}
return uri;
},
}
pgEscapeAndQuote(val) {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"));
},
}
expandFunctionParamList(params) {
if (_.isUndefined(params) || !_.isArray(params)) {
......@@ -695,12 +676,12 @@ const QueryGenerator = {
});
return paramList.join(', ');
},
}
expandOptions(options) {
return _.isUndefined(options) || _.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t');
},
}
decodeTriggerEventType(eventSpecifier) {
const EVENT_DECODER = {
......@@ -715,11 +696,11 @@ const QueryGenerator = {
}
return EVENT_DECODER[eventSpecifier];
},
}
triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constraint' ? 'CONSTRAINT ' : '';
},
}
expandTriggerEventSpec(fireOnSpec) {
if (_.isEmpty(fireOnSpec)) {
......@@ -747,7 +728,7 @@ const QueryGenerator = {
return eventSpec;
}).join(' OR ');
},
}
pgEnumName(tableName, attr, options) {
options = options || {};
......@@ -761,7 +742,7 @@ const QueryGenerator = {
}
return enumName;
},
}
pgListEnums(tableName, attrName, options) {
let enumName = '';
......@@ -775,7 +756,7 @@ const QueryGenerator = {
'JOIN pg_enum e ON t.oid = e.enumtypid ' +
'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
`WHERE n.nspname = '${tableDetails.schema}'${enumName} GROUP BY 1`;
},
}
pgEnum(tableName, attr, dataType, options) {
const enumName = this.pgEnumName(tableName, attr, options);
......@@ -792,7 +773,7 @@ const QueryGenerator = {
sql = this.pgEnumDrop(tableName, attr) + sql;
}
return sql;
},
}
pgEnumAdd(tableName, attr, value, options) {
const enumName = this.pgEnumName(tableName, attr);
......@@ -810,12 +791,12 @@ const QueryGenerator = {
}
return sql;
},
}
pgEnumDrop(tableName, attr, enumName) {
enumName = enumName || this.pgEnumName(tableName, attr);
return 'DROP TYPE IF EXISTS ' + enumName + '; ';
},
}
fromArray(text) {
text = text.replace(/^{/, '').replace(/}$/, '');
......@@ -828,11 +809,11 @@ const QueryGenerator = {
matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, ''));
return matches.slice(0, -1);
},
}
padInt(i) {
return i < 10 ? '0' + i.toString() : i.toString();
},
}
dataTypeMapping(tableName, attr, dataType) {
if (_.includes(dataType, 'PRIMARY KEY')) {
......@@ -854,21 +835,7 @@ const QueryGenerator = {
}
return dataType;
},
quoteIdentifier(identifier, force) {
if (identifier === '*') return identifier;
if (!force && this.options && this.options.quoteIdentifiers === false && identifier.indexOf('.') === -1 && identifier.indexOf('->') === -1) { // default is `true`
// In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase
// characters, they must always be double-quoted. This makes it
// impossible to write queries in portable SQL if tables are created in
// this way. Hence, we strip quotes if we don't want case sensitivity.
return Utils.removeTicks(identifier, '"');
} else {
return Utils.addTicks(Utils.removeTicks(identifier, '"'), '"');
}
},
}
/**
* Generates an SQL query that returns all foreign keys of a table.
......@@ -880,7 +847,7 @@ const QueryGenerator = {
getForeignKeysQuery(tableName) {
return 'SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r ' +
`WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '${tableName}' LIMIT 1) AND r.contype = 'f' ORDER BY 1;`;
},
}
/**
* Generate common SQL prefix for getForeignKeyReferencesQuery.
......@@ -904,7 +871,7 @@ const QueryGenerator = {
'ON tc.constraint_name = kcu.constraint_name ' +
'JOIN information_schema.constraint_column_usage AS ccu ' +
'ON ccu.constraint_name = tc.constraint_name ';
},
}
/**
* Generates an SQL query that returns all foreign keys details of a table.
......@@ -919,7 +886,7 @@ const QueryGenerator = {
`WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '${tableName}'` +
(catalogName ? ` AND tc.table_catalog = '${catalogName}'` : '') +
(schemaName ? ` AND tc.table_schema = '${schemaName}'` : '');
},
}
getForeignKeyReferenceQuery(table, columnName) {
const tableName = table.tableName || table;
......@@ -927,7 +894,7 @@ const QueryGenerator = {
return this._getForeignKeyReferencesQueryPrefix() +
`WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='${tableName}' AND kcu.column_name = '${columnName}'` +
(schema ? ` AND tc.table_schema = '${schema}'` : '');
},
}
/**
* Generates an SQL query that removes a foreign key from a table.
......@@ -939,7 +906,7 @@ const QueryGenerator = {
*/
dropForeignKeyQuery(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';';
},
}
setAutocommitQuery(value, options) {
if (options.parent) {
......@@ -957,8 +924,8 @@ const QueryGenerator = {
return;
}
return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options);
return super.setAutocommitQuery.call(this, value, options);
}
};
module.exports = QueryGenerator;
module.exports = PostgresQueryGenerator;
......@@ -12,8 +12,7 @@ class SqliteDialect extends AbstractDialect {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.QueryGenerator = _.extend({}, QueryGenerator, {
options: sequelize.options,
this.QueryGenerator = new QueryGenerator({
_dialect: this,
sequelize
});
......
......@@ -7,22 +7,18 @@ const _ = require('lodash');
const MySqlQueryGenerator = require('../mysql/query-generator');
const AbstractQueryGenerator = require('../abstract/query-generator');
const QueryGenerator = {
__proto__: MySqlQueryGenerator,
options: {},
dialect: 'sqlite',
class SQLiteQueryGenerator extends MySqlQueryGenerator {
createSchema() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
}
showSchemasQuery() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
}
versionQuery() {
return 'SELECT sqlite_version() as `version`';
},
}
createTableQuery(tableName, attributes, options) {
options = options || {};
......@@ -73,11 +69,11 @@ const QueryGenerator = {
const sql = `CREATE TABLE IF NOT EXISTS ${table} (${attrStr});`;
return this.replaceBooleanDefaults(sql);
},
}
booleanValue(value) {
return value ? 1 : 0;
},
}
/**
* Check whether the statmement is json function or simple path
......@@ -136,26 +132,7 @@ const QueryGenerator = {
// 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 = this.escape(['$']
.concat(paths)
.join('.')
.replace(/\.(\d+)(?:(?=\.)|$)/g, (_, digit) => `[${digit}]`));
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `json_extract(${quotedColumn}, ${pathStr})`;
},
}
//sqlite can't cast to datetime so we need to convert date values to their ISO strings
_toJSONValue(value) {
......@@ -165,7 +142,7 @@ const QueryGenerator = {
return value.map(val => val.toISOString());
}
return value;
},
}
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
......@@ -202,8 +179,8 @@ const QueryGenerator = {
}
}
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
},
return AbstractQueryGenerator.prototype.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
}
addColumnQuery(table, key, dataType) {
const attributes = {};
......@@ -214,11 +191,11 @@ const QueryGenerator = {
const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`;
return this.replaceBooleanDefaults(sql);
},
}
showTablesQuery() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
}
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
options.ignoreDuplicates = true;
......@@ -226,7 +203,7 @@ const QueryGenerator = {
const sql = this.insertQuery(tableName, insertValues, model.rawAttributes, options) + ' ' + this.updateQuery(tableName, updateValues, where, options, model.rawAttributes);
return sql;
},
}
updateQuery(tableName, attrValueHash, where, options, attributes) {
options = options || {};
......@@ -256,7 +233,7 @@ const QueryGenerator = {
} else {
return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where, options)}`;
}
},
}
deleteQuery(tableName, where, options, model) {
options = options || {};
......@@ -281,7 +258,7 @@ const QueryGenerator = {
}
return `DELETE FROM ${this.quoteTable(tableName)} ${whereClause}`;
},
}
attributesToSQL(attributes) {
const result = {};
......@@ -345,11 +322,11 @@ const QueryGenerator = {
}
return result;
},
}
showIndexesQuery(tableName) {
return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`;
},
}
showConstraintsQuery(tableName, constraintName) {
let sql = `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}'`;
......@@ -359,7 +336,7 @@ const QueryGenerator = {
}
return sql + ';';
},
}
removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes;
......@@ -369,7 +346,7 @@ const QueryGenerator = {
}
return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`;
},
}
describeTableQuery(tableName, schema, schemaDelimiter) {
const table = {
......@@ -378,11 +355,11 @@ const QueryGenerator = {
tableName
};
return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`;
},
}
describeCreateTableQuery(tableName) {
return `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}';`;
},
}
removeColumnQuery(tableName, attributes) {
......@@ -409,7 +386,7 @@ const QueryGenerator = {
+ this.createTableQuery(tableName, attributes)
+ `INSERT INTO ${quotedTableName} SELECT ${attributeNames} FROM ${quotedBackupTableName};`
+ `DROP TABLE ${quotedBackupTableName};`;
},
}
_alterConstraintQuery(tableName, attributes, createTableSql) {
let backupTableName;
......@@ -432,7 +409,7 @@ const QueryGenerator = {
+ `INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`
+ `DROP TABLE ${quotedTableName};`
+ `ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`;
},
}
renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) {
......@@ -462,7 +439,7 @@ const QueryGenerator = {
+ this.createTableQuery(tableName, attributes)
+ `INSERT INTO ${quotedTableName} SELECT ${attributeNamesExport} FROM ${quotedBackupTableName};`
+ `DROP TABLE ${quotedBackupTableName};`;
},
}
startTransactionQuery(transaction) {
if (transaction.parent) {
......@@ -470,12 +447,12 @@ const QueryGenerator = {
}
return 'BEGIN ' + transaction.options.type + ' TRANSACTION;';
},
}
setAutocommitQuery() {
// SQLite does not support SET autocommit
return null;
},
}
setIsolationLevelQuery(value) {
switch (value) {
......@@ -490,16 +467,11 @@ const QueryGenerator = {
default:
throw new Error('Unknown isolation level: ' + value);
}
},
}
replaceBooleanDefaults(sql) {
return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1');
},
quoteIdentifier(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
},
}
/**
* Generates an SQL query that returns all foreign keys of a table.
......@@ -513,4 +485,4 @@ const QueryGenerator = {
}
};
module.exports = QueryGenerator;
module.exports = SQLiteQueryGenerator;
\ No newline at end of file
......@@ -77,15 +77,11 @@ class Model {
const deletedAtCol = model._timestampAttributes.deletedAt;
const deletedAtAttribute = model.rawAttributes[deletedAtCol];
const deletedAtObject = {};
const now = Utils.now(this.sequelize.options.dialect);
let deletedAtDefaultValue = deletedAtAttribute.hasOwnProperty('defaultValue') ? deletedAtAttribute.defaultValue : null;
deletedAtDefaultValue = deletedAtDefaultValue || {
[Op.or]: {
[Op.gt]: now,
[Op.eq]: null
}
[Op.eq]: null
};
deletedAtObject[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue;
......@@ -1064,10 +1060,6 @@ class Model {
this._hasDefaultValues = !_.isEmpty(this._defaultValues);
// DEPRECATE: All code base is free from this.attributes now
// This should be removed in v5
this.attributes = this.rawAttributes;
this.tableAttributes = _.omit(this.rawAttributes, this._virtualAttributes);
this.prototype._hasCustomGetters = Object.keys(this.prototype._customGetters).length;
......@@ -4204,9 +4196,6 @@ class Model {
// Aliases
Model.prototype.updateAttributes = Model.prototype.update;
Model._conformOptions = Model._conformOptions;
Model._validateIncludedElements = Model._validateIncludedElements;
Model._expandIncludeAll = Model._expandIncludeAll;
Model.findByPrimary = Model.findById;
Model.find = Model.findOne;
Model.findAndCountAll = Model.findAndCount;
......
......@@ -78,8 +78,7 @@ const Op = {
values: Symbol.for('values'),
col: Symbol.for('col'),
placeholder: Symbol.for('placeholder'),
join: Symbol.for('join'),
raw: Symbol.for('raw') //deprecated remove by v5.0
join: Symbol.for('join')
};
const Aliases = {
......@@ -117,7 +116,6 @@ const Aliases = {
$all: Op.all,
$values: Op.values,
$col: Op.col,
$raw: Op.raw //deprecated remove by v5.0
};
const LegacyAliases = { //deprecated remove by v5.0
......
......@@ -4,29 +4,11 @@
"version": "0.0.0-development",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
"name": "Sascha Depold",
"email": "sascha@depold.com"
},
{
"name": "Jan Aagaard Meier",
"email": [
"janzeh@gmail.com",
"jmei@itu.dk"
]
},
{
"name": "Daniel Durante",
"email": "me@danieldurante.com"
},
{
"name": "Mick Hansen",
"email": "mick.kasper.hansen@gmail.com"
},
{
"name": "Sushant Dhiman",
"email": "sushantdhiman@outlook.com"
}
"Sascha Depold <sascha@depold.com>",
"Jan Aagaard Meier <janzeh@gmail.com>",
"Daniel Durante <me@danieldurante.com>",
"Mick Hansen <mick.kasper.hansen@gmail.com>",
"Sushant Dhiman <sushantdhiman@outlook.com>"
],
"repository": {
"type": "git",
......@@ -55,6 +37,8 @@
"wkx": "^0.4.1"
},
"devDependencies": {
"@semantic-release/github": "^4.2.6",
"@semantic-release/npm": "^3.2.2",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chai-datetime": "^1.5.0",
......@@ -144,18 +128,12 @@
"semantic-release": "semantic-release"
},
"engines": {
"node": ">=4.0.0"
"node": ">=6.0.0"
},
"license": "MIT",
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"release": {
"verifyConditions": [
"@semantic-release/npm",
"@semantic-release/github"
]
}
}
......@@ -15,56 +15,56 @@ if (dialect === 'mysql') {
username: { type: DataTypes.STRING, unique: true }
}, { timestamps: false });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
});
it('handles extended attributes (default)', function() {
const User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, defaultValue: 'foo'}
}, { timestamps: false });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
});
it('handles extended attributes (null)', function() {
const User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, allowNull: false}
}, { timestamps: false });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'});
});
it('handles extended attributes (primaryKey)', function() {
const User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, primaryKey: true}
}, { timestamps: false });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({username: 'VARCHAR(255) PRIMARY KEY'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({username: 'VARCHAR(255) PRIMARY KEY'});
});
it('adds timestamps', function() {
const User1 = this.sequelize.define('User' + config.rand(), {});
const User2 = this.sequelize.define('User' + config.rand(), {}, { timestamps: true });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User1.attributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User2.attributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User1.rawAttributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User2.rawAttributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
});
it('adds deletedAt if paranoid', function() {
const User = this.sequelize.define('User' + config.rand(), {}, { paranoid: true });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL'});
});
it('underscores timestamps if underscored', function() {
const User = this.sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true });
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.attributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL'});
expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL'});
});
it('omits text fields with defaultValues', function() {
const User = this.sequelize.define('User' + config.rand(), {name: {type: DataTypes.TEXT, defaultValue: 'helloworld'}});
expect(User.attributes.name.type.toString()).to.equal('TEXT');
expect(User.rawAttributes.name.type.toString()).to.equal('TEXT');
});
it('omits blobs fields with defaultValues', function() {
const User = this.sequelize.define('User' + config.rand(), {name: {type: DataTypes.STRING.BINARY, defaultValue: 'helloworld'}});
expect(User.attributes.name.type.toString()).to.equal('VARCHAR(255) BINARY');
expect(User.rawAttributes.name.type.toString()).to.equal('VARCHAR(255) BINARY');
});
});
......
......@@ -2198,8 +2198,6 @@ describe(Support.getTestDialectTeaser('Model'), () => {
describe('schematic support', () => {
beforeEach(function() {
const self = this;
this.UserPublic = this.sequelize.define('UserPublic', {
age: Sequelize.INTEGER
});
......@@ -2208,11 +2206,11 @@ describe(Support.getTestDialectTeaser('Model'), () => {
age: Sequelize.INTEGER
});
return self.sequelize.dropAllSchemas().then(() => {
return self.sequelize.createSchema('schema_test').then(() => {
return self.sequelize.createSchema('special').then(() => {
return self.UserSpecial.schema('special').sync({force: true}).then(UserSpecialSync => {
self.UserSpecialSync = UserSpecialSync;
return this.sequelize.dropAllSchemas().then(() => {
return this.sequelize.createSchema('schema_test').then(() => {
return this.sequelize.createSchema('special').then(() => {
return this.UserSpecial.schema('special').sync({force: true}).then(UserSpecialSync => {
this.UserSpecialSync = UserSpecialSync;
});
});
});
......
......@@ -5,7 +5,6 @@ const chai = require('chai'),
Sequelize = require('../../../index'),
Promise = Sequelize.Promise,
expect = chai.expect,
moment = require('moment'),
Support = require(__dirname + '/../support'),
dialect = Support.getTestDialect(),
DataTypes = require(__dirname + '/../../../lib/data-types'),
......@@ -991,31 +990,6 @@ describe(Support.getTestDialectTeaser('Model'), () => {
})).to.eventually.be.equal(null);
});
});
});
it('should find records where deletedAt set to future', function() {
const User = this.sequelize.define('paranoiduser', {
username: Sequelize.STRING
}, { paranoid: true });
return User.sync({ force: true }).then(() => {
return User.bulkCreate([
{username: 'Bob'},
{username: 'Tobi', deletedAt: moment().add(30, 'minutes').format()},
{username: 'Max', deletedAt: moment().add(30, 'days').format()},
{username: 'Tony', deletedAt: moment().subtract(30, 'days').format()}
]);
}).then(() => {
return User.find({ where: {username: 'Tobi'} });
}).then(tobi => {
expect(tobi).not.to.be.null;
}).then(() => {
return User.findAll();
}).then(users => {
expect(users.length).to.be.eql(3);
});
});
});
});
......@@ -214,7 +214,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).to.be.okay;
expect(created).to.be.ok;
}
});
});
......
......@@ -184,9 +184,11 @@ describe(Support.getTestDialectTeaser('Utils'), () => {
if (Support.getTestDialect() === 'postgres') {
describe('json', () => {
const queryGenerator = require('../../lib/dialects/postgres/query-generator.js');
beforeEach(function() {
this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator;
});
it('successfully parses a complex nested condition hash', () => {
it('successfully parses a complex nested condition hash', function() {
const conditions = {
metadata: {
language: 'icelandic',
......@@ -195,23 +197,23 @@ describe(Support.getTestDialectTeaser('Utils'), () => {
another_json_field: { x: 1 }
};
const 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(this.queryGenerator.handleSequelizeMethod(new Utils.Json(conditions))).to.deep.equal(expected);
});
it('successfully parses a string using dot notation', () => {
it('successfully parses a string using dot notation', function() {
const path = 'metadata.pg_rating.dk';
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal('("metadata"#>>\'{pg_rating,dk}\')');
expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal('("metadata"#>>\'{pg_rating,dk}\')');
});
it('allows postgres json syntax', () => {
it('allows postgres json syntax', function() {
const path = 'metadata->pg_rating->>dk';
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal(path);
expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path))).to.equal(path);
});
it('can take a value to compare against', () => {
it('can take a value to compare against', function() {
const path = 'metadata.pg_rating.is';
const value = 'U';
expect(queryGenerator.handleSequelizeMethod(new Utils.Json(path, value))).to.equal('("metadata"#>>\'{pg_rating,is}\') = \'U\'');
expect(this.queryGenerator.handleSequelizeMethod(new Utils.Json(path, value))).to.equal('("metadata"#>>\'{pg_rating,is}\') = \'U\'');
});
});
}
......
......@@ -156,11 +156,18 @@ const Support = {
},
getAbstractQueryGenerator(sequelize) {
return Object.assign(
{},
AbstractQueryGenerator,
{options: sequelize.options, _dialect: sequelize.dialect, sequelize, quoteIdentifier(identifier) { return identifier; }}
);
class ModdedQueryGenerator extends AbstractQueryGenerator {
quoteIdentifier(x) {
return x;
}
}
const queryGenerator = new ModdedQueryGenerator({
sequelize,
_dialect: sequelize.dialect
});
return queryGenerator;
},
getTestDialect() {
......
......@@ -6,15 +6,6 @@ const chai = require('chai'),
getAbstractQueryGenerator = require(__dirname + '/../../support').getAbstractQueryGenerator;
describe('QueryGenerator', () => {
describe('selectQuery', () => {
it('should generate correct query using array placeholder', function() {
const QG = getAbstractQueryGenerator(this.sequelize);
QG.selectQuery('foo', {where: {bar: {[Op.like]: {[Op.any]: ['a', 'b']}}}})
.should.be.equal('SELECT * FROM foo WHERE foo.bar LIKE ANY (\'a\', \'b\');');
});
});
describe('whereItemQuery', () => {
it('should generate correct query for Symbol operators', function() {
const QG = getAbstractQueryGenerator(this.sequelize);
......
......@@ -3,53 +3,54 @@
const Support = require(__dirname + '/../../support');
const expectsql = Support.expectsql;
const current = Support.sequelize;
const Operators = require('../../../../lib/operators');
const TableHints = require('../../../../lib/table-hints');
const QueryGenerator = require('../../../../lib/dialects/mssql/query-generator');
const _ = require('lodash');
if (current.dialect.name === 'mssql') {
suite('[MSSQL Specific] QueryGenerator', () => {
// Dialect would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
QueryGenerator._dialect = current.dialect;
//Aliases might not be needed here since it doesn't seem like this test uses any operators
QueryGenerator.setOperatorsAliases(Operators.Aliases);
test('getDefaultConstraintQuery', () => {
expectsql(QueryGenerator.getDefaultConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myColumn'), {
describe('[MSSQL Specific] QueryGenerator', () => {
before(function() {
this.queryGenerator = new QueryGenerator({
sequelize: this.sequelize,
_dialect: this.sequelize.dialect
});
});
it('getDefaultConstraintQuery', function() {
expectsql(this.queryGenerator.getDefaultConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myColumn'), {
mssql: "SELECT name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('[mySchema].[myTable]', 'U') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('myColumn') AND object_id = OBJECT_ID('[mySchema].[myTable]', 'U'));"
});
});
test('dropConstraintQuery', () => {
expectsql(QueryGenerator.dropConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myConstraint'), {
it('dropConstraintQuery', function() {
expectsql(this.queryGenerator.dropConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myConstraint'), {
mssql: 'ALTER TABLE [mySchema].[myTable] DROP CONSTRAINT [myConstraint];'
});
});
test('bulkInsertQuery', () => {
it('bulkInsertQuery', function() {
//normal cases
expectsql(QueryGenerator.bulkInsertQuery('myTable', [{ name: 'foo' }, {name: 'bar'}]), {
expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ name: 'foo' }, {name: 'bar'}]), {
mssql: "INSERT INTO [myTable] ([name]) VALUES (N'foo'),(N'bar');"
});
expectsql(QueryGenerator.bulkInsertQuery('myTable', [{ username: 'username', firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName'}]), {
expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ username: 'username', firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName'}]), {
mssql: "INSERT INTO [myTable] ([username],[firstName],[lastName]) VALUES (N'username',N'firstName',N'lastName'),(NULL,N'user1FirstName',N'user1LastName');"
});
expectsql(QueryGenerator.bulkInsertQuery('myTable', [{ firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName'}]), {
expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ firstName: 'firstName', lastName: 'lastName' }, { firstName: 'user1FirstName', lastName: 'user1LastName'}]), {
mssql: "INSERT INTO [myTable] ([firstName],[lastName]) VALUES (N'firstName',N'lastName'),(N'user1FirstName',N'user1LastName');"
});
//Bulk Insert With autogenerated primary key
const attributes = { id: { autoIncrement: true }};
expectsql(QueryGenerator.bulkInsertQuery('myTable', [{ id: null }], {}, attributes), {
expectsql(this.queryGenerator.bulkInsertQuery('myTable', [{ id: null }], {}, attributes), {
mssql: 'INSERT INTO [myTable] DEFAULT VALUES'
});
});
test('selectFromTableFragment', () => {
const modifiedGen = _.clone(QueryGenerator);
it('selectFromTableFragment', function() {
const modifiedGen = _.cloneDeep(this.queryGenerator);
// Test newer versions first
// Should be all the same since handling is done in addLimitAndOffset
// for SQL Server 2012 and higher (>= v11.0.0)
......@@ -113,76 +114,76 @@ if (current.dialect.name === 'mssql') {
});
});
test('getPrimaryKeyConstraintQuery', () => {
expectsql(QueryGenerator.getPrimaryKeyConstraintQuery('myTable', 'myColumnName'), {
it('getPrimaryKeyConstraintQuery', function() {
expectsql(this.queryGenerator.getPrimaryKeyConstraintQuery('myTable', 'myColumnName'), {
mssql: 'SELECT K.TABLE_NAME AS tableName, K.COLUMN_NAME AS columnName, K.CONSTRAINT_NAME AS constraintName FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K ON C.TABLE_NAME = K.TABLE_NAME AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\' AND K.COLUMN_NAME = \'myColumnName\' AND K.TABLE_NAME = \'myTable\';'
});
});
test('createSchema', () => {
expectsql(QueryGenerator.createSchema('mySchema'), {
it('createSchema', function() {
expectsql(this.queryGenerator.createSchema('mySchema'), {
mssql: 'IF NOT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'mySchema\' ) BEGIN EXEC sp_executesql N\'CREATE SCHEMA [mySchema] ;\' END;'
});
});
test('dropSchema', () => {
expectsql(QueryGenerator.dropSchema('mySchema'), {
it('dropSchema', function() {
expectsql(this.queryGenerator.dropSchema('mySchema'), {
mssql: 'IF EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'mySchema\' ) BEGIN DECLARE @id INT, @ms_sql NVARCHAR(2000); DECLARE @cascade TABLE ( id INT NOT NULL IDENTITY PRIMARY KEY, ms_sql NVARCHAR(2000) NOT NULL ); INSERT INTO @cascade ( ms_sql ) SELECT CASE WHEN o.type IN (\'F\',\'PK\') THEN N\'ALTER TABLE [\'+ s.name + N\'].[\' + p.name + N\'] DROP CONSTRAINT [\' + o.name + N\']\' ELSE N\'DROP TABLE [\'+ s.name + N\'].[\' + o.name + N\']\' END FROM sys.objects o JOIN sys.schemas s on o.schema_id = s.schema_id LEFT OUTER JOIN sys.objects p on o.parent_object_id = p.object_id WHERE o.type IN (\'F\', \'PK\', \'U\') AND s.name = \'mySchema\' ORDER BY o.type ASC; SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id; WHILE @id IS NOT NULL BEGIN BEGIN TRY EXEC sp_executesql @ms_sql; END TRY BEGIN CATCH BREAK; THROW; END CATCH; DELETE FROM @cascade WHERE id = @id; SELECT @id = NULL, @ms_sql = NULL; SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id; END EXEC sp_executesql N\'DROP SCHEMA [mySchema] ;\' END;'
});
});
test('showSchemasQuery', () => {
expectsql(QueryGenerator.showSchemasQuery(), {
it('showSchemasQuery', function() {
expectsql(this.queryGenerator.showSchemasQuery(), {
mssql: 'SELECT "name" as "schema_name" FROM sys.schemas as s WHERE "s"."name" NOT IN ( \'INFORMATION_SCHEMA\', \'dbo\', \'guest\', \'sys\', \'archive\' ) AND "s"."name" NOT LIKE \'db_%\''
});
});
test('versionQuery', () => {
expectsql(QueryGenerator.versionQuery(), {
it('versionQuery', function() {
expectsql(this.queryGenerator.versionQuery(), {
mssql: "DECLARE @ms_ver NVARCHAR(20); SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion'))); SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'"
});
});
test('renameTableQuery', () => {
expectsql(QueryGenerator.renameTableQuery('oldTableName', 'newTableName'), {
it('renameTableQuery', function() {
expectsql(this.queryGenerator.renameTableQuery('oldTableName', 'newTableName'), {
mssql: 'EXEC sp_rename [oldTableName], [newTableName];'
});
});
test('showTablesQuery', () => {
expectsql(QueryGenerator.showTablesQuery(), {
it('showTablesQuery', function() {
expectsql(this.queryGenerator.showTablesQuery(), {
mssql: 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;'
});
});
test('dropTableQuery', () => {
expectsql(QueryGenerator.dropTableQuery('dirtyTable'), {
it('dropTableQuery', function() {
expectsql(this.queryGenerator.dropTableQuery('dirtyTable'), {
mssql: "IF OBJECT_ID('[dirtyTable]', 'U') IS NOT NULL DROP TABLE [dirtyTable];"
});
});
test('removeColumnQuery', () => {
expectsql(QueryGenerator.removeColumnQuery('myTable', 'myColumn'), {
it('removeColumnQuery', function() {
expectsql(this.queryGenerator.removeColumnQuery('myTable', 'myColumn'), {
mssql: 'ALTER TABLE [myTable] DROP COLUMN [myColumn];'
});
});
test('quoteIdentifier', () => {
expectsql(QueryGenerator.quoteIdentifier("'myTable'.'Test'"), {
it('quoteIdentifier', function() {
expectsql(this.queryGenerator.quoteIdentifier("'myTable'.'Test'"), {
mssql: '[myTable.Test]'
});
});
test('getForeignKeysQuery', () => {
expectsql(QueryGenerator.getForeignKeysQuery('myTable'), {
it('getForeignKeysQuery', function() {
expectsql(this.queryGenerator.getForeignKeysQuery('myTable'), {
mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM SYS.FOREIGN_KEY_COLUMNS FKC INNER JOIN SYS.OBJECTS OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN SYS.TABLES TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN SYS.COLUMNS COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN SYS.TABLES RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN SYS.COLUMNS RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable'"
});
expectsql(QueryGenerator.getForeignKeysQuery('myTable', 'myDatabase'), {
expectsql(this.queryGenerator.getForeignKeysQuery('myTable', 'myDatabase'), {
mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintCatalog = 'myDatabase', constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), tableCatalog = 'myDatabase', columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedCatalog = 'myDatabase', referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM SYS.FOREIGN_KEY_COLUMNS FKC INNER JOIN SYS.OBJECTS OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN SYS.TABLES TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN SYS.COLUMNS COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN SYS.TABLES RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN SYS.COLUMNS RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable'"
});
expectsql(QueryGenerator.getForeignKeysQuery({
expectsql(this.queryGenerator.getForeignKeysQuery({
tableName: 'myTable',
schema: 'mySchema'
}, 'myDatabase'), {
......@@ -190,11 +191,11 @@ if (current.dialect.name === 'mssql') {
});
});
test('getForeignKeyQuery', () => {
expectsql(QueryGenerator.getForeignKeyQuery('myTable', 'myColumn'), {
it('getForeignKeyQuery', function() {
expectsql(this.queryGenerator.getForeignKeyQuery('myTable', 'myColumn'), {
mssql: "SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM SYS.FOREIGN_KEY_COLUMNS FKC INNER JOIN SYS.OBJECTS OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN SYS.TABLES TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN SYS.COLUMNS COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN SYS.TABLES RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN SYS.COLUMNS RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='myTable' AND COL.NAME ='myColumn'"
});
expectsql(QueryGenerator.getForeignKeyQuery({
expectsql(this.queryGenerator.getForeignKeyQuery({
tableName: 'myTable',
schema: 'mySchema'
}, 'myColumn'), {
......@@ -202,40 +203,52 @@ if (current.dialect.name === 'mssql') {
});
});
test('dropForeignKeyQuery', () => {
expectsql(QueryGenerator.dropForeignKeyQuery('myTable', 'myColumnKey'), {
it('dropForeignKeyQuery', function() {
expectsql(this.queryGenerator.dropForeignKeyQuery('myTable', 'myColumnKey'), {
mssql: 'ALTER TABLE [myTable] DROP [myColumnKey]'
});
});
test('arithmeticQuery', () => {
[{
title: 'Should use the plus operator',
arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}],
expectation: 'UPDATE myTable SET foo=foo+ \'bar\' '
},
{
title: 'Should use the plus operator with where clause',
arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz'}, {}],
expectation: 'UPDATE myTable SET foo=foo+ \'bar\' WHERE bar = \'biz\''
},
{
title: 'Should use the minus operator',
arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}],
expectation: 'UPDATE myTable SET foo=foo- \'bar\' '
},
{
title: 'Should use the minus operator with negative value',
arguments: ['-', 'myTable', { foo: -1 }, {}, {}],
expectation: 'UPDATE myTable SET foo=foo- -1 '
},
{
title: 'Should use the minus operator with where clause',
arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz'}, {}],
expectation: 'UPDATE myTable SET foo=foo- \'bar\' WHERE bar = \'biz\''
}].forEach(test => {
it(test.title, () => {
expectsql(QueryGenerator.arithmeticQuery.call(QueryGenerator, test.arguments), {
describe('arithmeticQuery', () => {
[
{
title: 'Should use the plus operator',
arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}],
expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.* '
},
{
title: 'Should use the plus operator with where clause',
arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz'}, {}],
expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\''
},
{
title: 'Should use the plus operator without returning clause',
arguments: ['+', 'myTable', { foo: 'bar' }, {}, { returning: false }],
expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' '
},
{
title: 'Should use the minus operator',
arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}],
expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.* '
},
{
title: 'Should use the minus operator with negative value',
arguments: ['-', 'myTable', { foo: -1 }, {}, {}],
expectation: 'UPDATE [myTable] SET [foo]=[foo]- -1 OUTPUT INSERTED.* '
},
{
title: 'Should use the minus operator with where clause',
arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz'}, {}],
expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\''
},
{
title: 'Should use the minus operator without returning clause',
arguments: ['-', 'myTable', { foo: 'bar' }, {}, { returning: false }],
expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' '
}
].forEach(test => {
it(test.title, function() {
expectsql(this.queryGenerator.arithmeticQuery.apply(this.queryGenerator, test.arguments), {
mssql: test.expectation
});
});
......
......@@ -608,20 +608,26 @@ if (dialect === 'mysql') {
_.each(suites, (tests, suiteTitle) => {
describe(suiteTitle, () => {
beforeEach(function() {
this.queryGenerator = new QueryGenerator({
sequelize: this.sequelize,
_dialect: this.sequelize.dialect
});
});
tests.forEach(test => {
const title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + JSON.stringify(test.arguments);
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
const context = test.context || {options: {}};
if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
}
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
QueryGenerator.sequelize = this.sequelize;
QueryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {});
this.queryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = this.queryGenerator[suiteTitle].apply(this.queryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
});
});
......
......@@ -984,27 +984,26 @@ if (dialect.match(/^postgres/)) {
_.each(suites, (tests, suiteTitle) => {
describe(suiteTitle, () => {
afterEach(function() {
this.sequelize.options.quoteIdentifiers = true;
QueryGenerator.options.quoteIdentifiers = true;
beforeEach(function() {
this.queryGenerator = new QueryGenerator({
sequelize: this.sequelize,
_dialect: this.sequelize.dialect
});
});
tests.forEach(test => {
const title = test.title || 'Postgres correctly returns ' + test.expectation + ' for ' + JSON.stringify(test.arguments);
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
const context = test.context || {options: {}};
if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
}
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
QueryGenerator.sequelize = this.sequelize;
QueryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {});
this.queryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = this.queryGenerator[suiteTitle].apply(this.queryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
});
});
......
......@@ -539,20 +539,26 @@ if (dialect === 'sqlite') {
_.each(suites, (tests, suiteTitle) => {
describe(suiteTitle, () => {
beforeEach(function() {
this.queryGenerator = new QueryGenerator({
sequelize: this.sequelize,
_dialect: this.sequelize.dialect
});
});
tests.forEach(test => {
const title = test.title || 'SQLite correctly returns ' + test.expectation + ' for ' + JSON.stringify(test.arguments);
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
const context = test.context || {options: {}};
if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
if (_.isFunction(test.arguments[2])) test.arguments[2] = test.arguments[2](this.sequelize);
}
QueryGenerator.options = _.assign(context.options, { timezone: '+00:00' });
QueryGenerator._dialect = this.sequelize.dialect;
QueryGenerator.sequelize = this.sequelize;
QueryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments);
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {});
this.queryGenerator.setOperatorsAliases(Operators.LegacyAliases);
const conditions = this.queryGenerator[suiteTitle].apply(this.queryGenerator, test.arguments);
expect(conditions).to.deep.equal(test.expectation);
});
});
......
......@@ -3,8 +3,6 @@
const Support = require(__dirname + '/../support'),
DataTypes = require(__dirname + '/../../../lib/data-types'),
util = require('util'),
chai = require('chai'),
expect = chai.expect,
expectsql = Support.expectsql,
current = Support.sequelize,
sql = current.dialect.QueryGenerator;
......@@ -406,16 +404,6 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
});
});
suite('$raw', () => {
test('should fail on $raw', () => {
expect(() => {
sql.whereItemQuery('rank', {
$raw: 'AGHJZ'
});
}).to.throw(Error, 'The `$raw` where property is no longer supported. Use `sequelize.literal` instead.');
});
});
suite('$like', () => {
testsql('username', {
$like: '%swagger'
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!