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

Commit 0fb4bb11 by Pedro Augusto de Paula Barbosa Committed by GitHub

refactor(lib): new `joinSQLFragments` util (#11865)

1 parent 91416e3b
...@@ -10,6 +10,7 @@ const moment = require('moment'); ...@@ -10,6 +10,7 @@ const moment = require('moment');
const { logger } = require('./utils/logger'); const { logger } = require('./utils/logger');
const warnings = {}; const warnings = {};
const { classToInvokable } = require('./utils/classToInvokable'); const { classToInvokable } = require('./utils/classToInvokable');
const { joinSQLFragments } = require('./utils/join-sql-fragments');
class ABSTRACT { class ABSTRACT {
toString(options) { toString(options) {
...@@ -62,7 +63,10 @@ class STRING extends ABSTRACT { ...@@ -62,7 +63,10 @@ class STRING extends ABSTRACT {
this._length = options.length || 255; this._length = options.length || 255;
} }
toSql() { toSql() {
return `VARCHAR(${this._length})${this._binary ? ' BINARY' : ''}`; return joinSQLFragments([
`VARCHAR(${this._length})`,
this._binary && 'BINARY'
]);
} }
validate(value) { validate(value) {
if (Object.prototype.toString.call(value) !== '[object String]') { if (Object.prototype.toString.call(value) !== '[object String]') {
...@@ -97,7 +101,10 @@ class CHAR extends STRING { ...@@ -97,7 +101,10 @@ class CHAR extends STRING {
super(typeof length === 'object' && length || { length, binary }); super(typeof length === 'object' && length || { length, binary });
} }
toSql() { toSql() {
return `CHAR(${this._length})${this._binary ? ' BINARY' : ''}`; return joinSQLFragments([
`CHAR(${this._length})`,
this._binary && 'BINARY'
]);
} }
} }
......
...@@ -310,7 +310,19 @@ class QueryGenerator { ...@@ -310,7 +310,19 @@ class QueryGenerator {
returning += returnValues.returningFragment; returning += returnValues.returningFragment;
} }
return `INSERT${ignoreDuplicates} INTO ${this.quoteTable(tableName)} (${attributes}) VALUES ${tuples.join(',')}${onDuplicateKeyUpdate}${onConflictDoNothing}${returning};`; return Utils.joinSQLFragments([
'INSERT',
ignoreDuplicates,
'INTO',
this.quoteTable(tableName),
`(${attributes})`,
'VALUES',
tuples.join(','),
onDuplicateKeyUpdate,
onConflictDoNothing,
returning,
';'
]);
} }
/** /**
...@@ -446,7 +458,15 @@ class QueryGenerator { ...@@ -446,7 +458,15 @@ class QueryGenerator {
updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); updateSetSqlFragments.push(`${quotedField}=${escapedValue}`);
} }
return `UPDATE ${this.quoteTable(tableName)} SET ${updateSetSqlFragments.join(',')}${outputFragment} ${this.whereQuery(where)}${returningFragment}`.trim(); return Utils.joinSQLFragments([
'UPDATE',
this.quoteTable(tableName),
'SET',
updateSetSqlFragments.join(','),
outputFragment,
this.whereQuery(where),
returningFragment
]);
} }
/* /*
...@@ -570,16 +590,19 @@ class QueryGenerator { ...@@ -570,16 +590,19 @@ class QueryGenerator {
} }
addConstraintQuery(tableName, options) { addConstraintQuery(tableName, options) {
options = options || {};
const constraintSnippet = this.getConstraintSnippet(tableName, options);
if (typeof tableName === 'string') { if (typeof tableName === 'string') {
tableName = this.quoteIdentifiers(tableName); tableName = this.quoteIdentifiers(tableName);
} else { } else {
tableName = this.quoteTable(tableName); tableName = this.quoteTable(tableName);
} }
return `ALTER TABLE ${tableName} ADD ${constraintSnippet};`; return Utils.joinSQLFragments([
'ALTER TABLE',
tableName,
'ADD',
this.getConstraintSnippet(tableName, options || {}),
';'
]);
} }
getConstraintSnippet(tableName, options) { getConstraintSnippet(tableName, options) {
...@@ -660,7 +683,12 @@ class QueryGenerator { ...@@ -660,7 +683,12 @@ class QueryGenerator {
tableName = this.quoteTable(tableName); tableName = this.quoteTable(tableName);
} }
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${this.quoteIdentifiers(constraintName)}`; return Utils.joinSQLFragments([
'ALTER TABLE',
tableName,
'DROP CONSTRAINT',
this.quoteIdentifiers(constraintName)
]);
} }
/* /*
...@@ -1486,7 +1514,11 @@ class QueryGenerator { ...@@ -1486,7 +1514,11 @@ class QueryGenerator {
alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options); alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options);
} }
return `${prefix} AS ${this.quoteIdentifier(alias, true)}`; return Utils.joinSQLFragments([
prefix,
'AS',
this.quoteIdentifier(alias, true)
]);
}); });
if (include.subQuery && topLevelInfo.subQuery) { if (include.subQuery && topLevelInfo.subQuery) {
for (const attr of includeAttributes) { for (const attr of includeAttributes) {
...@@ -1750,9 +1782,11 @@ class QueryGenerator { ...@@ -1750,9 +1782,11 @@ class QueryGenerator {
alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options); alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options);
} }
return `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) return Utils.joinSQLFragments([
} AS ${ `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)}`,
this.quoteIdentifier(alias)}`; 'AS',
this.quoteIdentifier(alias)
]);
}); });
const association = include.association; const association = include.association;
const parentIsTop = !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name; const parentIsTop = !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name;
...@@ -2130,15 +2164,17 @@ class QueryGenerator { ...@@ -2130,15 +2164,17 @@ class QueryGenerator {
return `CAST(${result} AS ${smth.type.toUpperCase()})`; return `CAST(${result} AS ${smth.type.toUpperCase()})`;
} }
if (smth instanceof Utils.Fn) { if (smth instanceof Utils.Fn) {
return `${smth.fn}(${smth.args.map(arg => { return `${smth.fn}(${
if (arg instanceof Utils.SequelizeMethod) { smth.args.map(arg => {
return this.handleSequelizeMethod(arg, tableName, factory, options, prepend); if (arg instanceof Utils.SequelizeMethod) {
} return this.handleSequelizeMethod(arg, tableName, factory, options, prepend);
if (_.isPlainObject(arg)) { }
return this.whereItemsQuery(arg); if (_.isPlainObject(arg)) {
} return this.whereItemsQuery(arg);
return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg); }
}).join(', ')})`; return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg);
}).join(', ')
})`;
} }
if (smth instanceof Utils.Col) { if (smth instanceof Utils.Col) {
if (Array.isArray(smth.col) && !factory) { if (Array.isArray(smth.col) && !factory) {
......
...@@ -51,7 +51,7 @@ module.exports = BaseTypes => { ...@@ -51,7 +51,7 @@ module.exports = BaseTypes => {
class DATE extends BaseTypes.DATE { class DATE extends BaseTypes.DATE {
toSql() { toSql() {
return `DATETIME${this._length ? `(${this._length})` : ''}`; return this._length ? `DATETIME(${this._length})` : 'DATETIME';
} }
_stringify(date, options) { _stringify(date, options) {
date = this._applyTimezone(date, options); date = this._applyTimezone(date, options);
......
'use strict'; 'use strict';
const MySQLQueryGenerator = require('../mysql/query-generator'); const MySQLQueryGenerator = require('../mysql/query-generator');
const Utils = require('./../../utils');
class MariaDBQueryGenerator extends MySQLQueryGenerator { class MariaDBQueryGenerator extends MySQLQueryGenerator {
createSchema(schema, options) { createSchema(schema, options) {
...@@ -9,10 +10,13 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { ...@@ -9,10 +10,13 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator {
collate: null collate: null
}, options || {}); }, options || {});
const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; return Utils.joinSQLFragments([
const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; 'CREATE SCHEMA IF NOT EXISTS',
this.quoteIdentifier(schema),
return `CREATE SCHEMA IF NOT EXISTS ${this.quoteIdentifier(schema)}${charset}${collate};`; options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`,
options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`,
';'
]);
} }
dropSchema(schema) { dropSchema(schema) {
...@@ -20,8 +24,22 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { ...@@ -20,8 +24,22 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator {
} }
showSchemasQuery(options) { showSchemasQuery(options) {
const skip = options.skip && Array.isArray(options.skip) && options.skip.length > 0 ? options.skip : null; const schemasToSkip = [
return `SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA'${skip ? skip.reduce( (sql, schemaName) => sql += `,${this.escape(schemaName)}`, '') : ''});`; '\'MYSQL\'',
'\'INFORMATION_SCHEMA\'',
'\'PERFORMANCE_SCHEMA\''
];
if (options.skip && Array.isArray(options.skip) && options.skip.length > 0) {
for (const schemaName of options.skip) {
schemasToSkip.push(this.escape(schemaName));
}
}
return Utils.joinSQLFragments([
'SELECT SCHEMA_NAME as schema_name',
'FROM INFORMATION_SCHEMA.SCHEMATA',
`WHERE SCHEMA_NAME NOT IN (${schemasToSkip.join(', ')})`,
';'
]);
} }
showTablesQuery(database) { showTablesQuery(database) {
......
...@@ -107,10 +107,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -107,10 +107,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} }
createTableQuery(tableName, attributes, options) { createTableQuery(tableName, attributes, options) {
const query = (table, attrs) => `IF OBJECT_ID('${table}', 'U') IS NULL CREATE TABLE ${table} (${attrs})`, const primaryKeys = [],
primaryKeys = [],
foreignKeys = {}, foreignKeys = {},
attrStr = []; attributesClauseParts = [];
let commentStr = ''; let commentStr = '';
...@@ -133,24 +132,22 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -133,24 +132,22 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
if (dataType.includes('REFERENCES')) { if (dataType.includes('REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end // MSSQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/); match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
foreignKeys[attr] = match[2]; foreignKeys[attr] = match[2];
} else { } else {
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
} }
} else if (dataType.includes('REFERENCES')) { } else if (dataType.includes('REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end // MSSQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/); match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
foreignKeys[attr] = match[2]; foreignKeys[attr] = match[2];
} else { } else {
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`);
} }
} }
} }
let attributesClause = attrStr.join(', ');
const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');
if (options.uniqueKeys) { if (options.uniqueKeys) {
...@@ -159,22 +156,33 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -159,22 +156,33 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
if (typeof indexName !== 'string') { if (typeof indexName !== 'string') {
indexName = `uniq_${tableName}_${columns.fields.join('_')}`; indexName = `uniq_${tableName}_${columns.fields.join('_')}`;
} }
attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; attributesClauseParts.push(`CONSTRAINT ${
this.quoteIdentifier(indexName)
} UNIQUE (${
columns.fields.map(field => this.quoteIdentifier(field)).join(', ')
})`);
} }
}); });
} }
if (pkString.length > 0) { if (pkString.length > 0) {
attributesClause += `, PRIMARY KEY (${pkString})`; attributesClauseParts.push(`PRIMARY KEY (${pkString})`);
} }
for (const fkey in foreignKeys) { for (const fkey in foreignKeys) {
if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) {
attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; attributesClauseParts.push(`FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`);
} }
} }
return `${query(this.quoteTable(tableName), attributesClause)};${commentStr}`; const quotedTableName = this.quoteTable(tableName);
return Utils.joinSQLFragments([
`IF OBJECT_ID('${quotedTableName}', 'U') IS NULL`,
`CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`,
';',
commentStr
]);
} }
describeTableQuery(tableName, schema) { describeTableQuery(tableName, schema) {
...@@ -226,8 +234,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -226,8 +234,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} }
dropTableQuery(tableName) { dropTableQuery(tableName) {
const qouteTbl = this.quoteTable(tableName); const quoteTbl = this.quoteTable(tableName);
return `IF OBJECT_ID('${qouteTbl}', 'U') IS NOT NULL DROP TABLE ${qouteTbl};`; return Utils.joinSQLFragments([
`IF OBJECT_ID('${quoteTbl}', 'U') IS NOT NULL`,
'DROP TABLE',
quoteTbl,
';'
]);
} }
addColumnQuery(table, key, dataType) { addColumnQuery(table, key, dataType) {
...@@ -244,10 +257,15 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -244,10 +257,15 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
delete dataType['comment']; delete dataType['comment'];
} }
const def = this.attributeToSQL(dataType, { return Utils.joinSQLFragments([
context: 'addColumn' 'ALTER TABLE',
}); this.quoteTable(table),
return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${def};${commentStr}`; 'ADD',
this.quoteIdentifier(key),
this.attributeToSQL(dataType, { context: 'addColumn' }),
';',
commentStr
]);
} }
commentTemplate(comment, table, column) { commentTemplate(comment, table, column) {
...@@ -259,7 +277,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -259,7 +277,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} }
removeColumnQuery(tableName, attributeName) { removeColumnQuery(tableName, attributeName) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP COLUMN ${this.quoteIdentifier(attributeName)};`; return Utils.joinSQLFragments([
'ALTER TABLE',
this.quoteTable(tableName),
'DROP COLUMN',
this.quoteIdentifier(attributeName),
';'
]);
} }
changeColumnQuery(tableName, attributes) { changeColumnQuery(tableName, attributes) {
...@@ -284,21 +308,25 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -284,21 +308,25 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} }
} }
let finalQuery = ''; return Utils.joinSQLFragments([
if (attrString.length) { 'ALTER TABLE',
finalQuery += `ALTER COLUMN ${attrString.join(', ')}`; this.quoteTable(tableName),
finalQuery += constraintString.length ? ' ' : ''; attrString.length && `ALTER COLUMN ${attrString.join(', ')}`,
} constraintString.length && `ADD ${constraintString.join(', ')}`,
if (constraintString.length) { ';',
finalQuery += `ADD ${constraintString.join(', ')}`; commentString
} ]);
return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};${commentString}`;
} }
renameColumnQuery(tableName, attrBefore, attributes) { renameColumnQuery(tableName, attrBefore, attributes) {
const newName = Object.keys(attributes)[0]; const newName = Object.keys(attributes)[0];
return `EXEC sp_rename '${this.quoteTable(tableName)}.${attrBefore}', '${newName}', 'COLUMN';`; return Utils.joinSQLFragments([
'EXEC sp_rename',
`'${this.quoteTable(tableName)}.${attrBefore}',`,
`'${newName}',`,
"'COLUMN'",
';'
]);
} }
bulkInsertQuery(tableName, attrValueHashes, options, attributes) { bulkInsertQuery(tableName, attrValueHashes, options, attributes) {
...@@ -499,19 +527,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -499,19 +527,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
deleteQuery(tableName, where, options = {}, model) { deleteQuery(tableName, where, options = {}, model) {
const table = this.quoteTable(tableName); const table = this.quoteTable(tableName);
const whereClause = this.getWhereConditions(where, null, model, options);
let whereClause = this.getWhereConditions(where, null, model, options); return Utils.joinSQLFragments([
let limit = ''; 'DELETE',
options.limit && `TOP(${this.escape(options.limit)})`,
if (options.limit) { 'FROM',
limit = ` TOP(${this.escape(options.limit)})`; table,
} whereClause && `WHERE ${whereClause}`,
';',
if (whereClause) { 'SELECT @@ROWCOUNT AS AFFECTEDROWS',
whereClause = ` WHERE ${whereClause}`; ';'
} ]);
return `DELETE${limit} FROM ${table}${whereClause}; SELECT @@ROWCOUNT AS AFFECTEDROWS;`;
} }
showIndexesQuery(tableName) { showIndexesQuery(tableName) {
...@@ -718,20 +745,19 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -718,20 +745,19 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
getForeignKeyQuery(table, attributeName) { getForeignKeyQuery(table, attributeName) {
const tableName = table.tableName || table; const tableName = table.tableName || table;
let sql = `${this._getForeignKeysQueryPrefix() return Utils.joinSQLFragments([
} WHERE TB.NAME =${wrapSingleQuote(tableName) this._getForeignKeysQueryPrefix(),
} AND COL.NAME =${wrapSingleQuote(attributeName)}`; 'WHERE',
`TB.NAME =${wrapSingleQuote(tableName)}`,
if (table.schema) { 'AND',
sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`; `COL.NAME =${wrapSingleQuote(attributeName)}`,
} table.schema && `AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`
]);
return sql;
} }
getPrimaryKeyConstraintQuery(table, attributeName) { getPrimaryKeyConstraintQuery(table, attributeName) {
const tableName = wrapSingleQuote(table.tableName || table); const tableName = wrapSingleQuote(table.tableName || table);
return [ return Utils.joinSQLFragments([
'SELECT K.TABLE_NAME AS tableName,', 'SELECT K.TABLE_NAME AS tableName,',
'K.COLUMN_NAME AS columnName,', 'K.COLUMN_NAME AS columnName,',
'K.CONSTRAINT_NAME AS constraintName', 'K.CONSTRAINT_NAME AS constraintName',
...@@ -743,24 +769,39 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -743,24 +769,39 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME', 'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME',
'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'', 'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'',
`AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`, `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`,
`AND K.TABLE_NAME = ${tableName};` `AND K.TABLE_NAME = ${tableName}`,
].join(' '); ';'
]);
} }
dropForeignKeyQuery(tableName, foreignKey) { dropForeignKeyQuery(tableName, foreignKey) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(foreignKey)}`; return Utils.joinSQLFragments([
'ALTER TABLE',
this.quoteTable(tableName),
'DROP',
this.quoteIdentifier(foreignKey)
]);
} }
getDefaultConstraintQuery(tableName, attributeName) { getDefaultConstraintQuery(tableName, attributeName) {
const quotedTable = this.quoteTable(tableName); const quotedTable = this.quoteTable(tableName);
return 'SELECT name FROM sys.default_constraints ' + return Utils.joinSQLFragments([
`WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U') ` + 'SELECT name FROM sys.default_constraints',
`AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}') ` + `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U')`,
`AND object_id = OBJECT_ID('${quotedTable}', 'U'));`; `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}')`,
`AND object_id = OBJECT_ID('${quotedTable}', 'U'))`,
';'
]);
} }
dropConstraintQuery(tableName, constraintName) { dropConstraintQuery(tableName, constraintName) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(constraintName)};`; return Utils.joinSQLFragments([
'ALTER TABLE',
this.quoteTable(tableName),
'DROP CONSTRAINT',
this.quoteIdentifier(constraintName),
';'
]);
} }
setIsolationLevelQuery() { setIsolationLevelQuery() {
...@@ -796,60 +837,64 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { ...@@ -796,60 +837,64 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} }
selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) {
let topFragment = ''; const dbVersion = this.sequelize.options.databaseVersion;
let mainFragment = `SELECT ${attributes.join(', ')} FROM ${tables}`; const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0');
// Handle SQL Server 2008 with TOP instead of LIMIT if (isSQLServer2008 && options.offset) {
if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) { // For earlier versions of SQL server, we need to nest several queries
if (options.limit) { // in order to emulate the OFFSET behavior.
topFragment = `TOP ${options.limit} `; //
// 1. The outermost query selects all items from the inner query block.
// This is due to a limitation in SQL server with the use of computed
// columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses.
// 2. The next query handles the LIMIT and OFFSET behavior by getting
// the TOP N rows of the query where the row number is > OFFSET
// 3. The innermost query is the actual set we want information from
const offset = options.offset || 0;
const isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
let orders = { mainQueryOrder: [] };
if (options.order) {
orders = this.getQueryOrders(options, model, isSubQuery);
} }
if (options.offset) {
const offset = options.offset || 0,
isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
let orders = { mainQueryOrder: [] };
if (options.order) {
orders = this.getQueryOrders(options, model, isSubQuery);
}
if (!orders.mainQueryOrder.length) {
orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField));
}
const tmpTable = mainTableAs ? mainTableAs : 'OffsetTable'; if (orders.mainQueryOrder.length === 0) {
const whereFragment = where ? ` WHERE ${where}` : ''; orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField));
/*
* For earlier versions of SQL server, we need to nest several queries
* in order to emulate the OFFSET behavior.
*
* 1. The outermost query selects all items from the inner query block.
* This is due to a limitation in SQL server with the use of computed
* columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses.
* 2. The next query handles the LIMIT and OFFSET behavior by getting
* the TOP N rows of the query where the row number is > OFFSET
* 3. The innermost query is the actual set we want information from
*/
const fragment = `SELECT TOP 100 PERCENT ${attributes.join(', ')} FROM ` +
`(SELECT ${topFragment}*` +
` FROM (SELECT ROW_NUMBER() OVER (ORDER BY ${orders.mainQueryOrder.join(', ')}) as row_num, * ` +
` FROM ${tables} AS ${tmpTable}${whereFragment})` +
` AS ${tmpTable} WHERE row_num > ${offset})` +
` AS ${tmpTable}`;
return fragment;
} }
mainFragment = `SELECT ${topFragment}${attributes.join(', ')} FROM ${tables}`;
}
if (mainTableAs) { const tmpTable = mainTableAs || 'OffsetTable';
mainFragment += ` AS ${mainTableAs}`;
} return Utils.joinSQLFragments([
'SELECT TOP 100 PERCENT',
if (options.tableHint && TableHints[options.tableHint]) { attributes.join(', '),
mainFragment += ` WITH (${TableHints[options.tableHint]})`; 'FROM (',
} [
'SELECT',
return mainFragment; options.limit && `TOP ${options.limit}`,
'* FROM (',
[
'SELECT ROW_NUMBER() OVER (',
[
'ORDER BY',
orders.mainQueryOrder.join(', ')
],
`) as row_num, * FROM ${tables} AS ${tmpTable}`,
where && `WHERE ${where}`
],
`) AS ${tmpTable} WHERE row_num > ${offset}`
],
`) AS ${tmpTable}`
]);
}
return Utils.joinSQLFragments([
'SELECT',
isSQLServer2008 && options.limit && `TOP ${options.limit}`,
attributes.join(', '),
`FROM ${tables}`,
mainTableAs && `AS ${mainTableAs}`,
options.tableHint && TableHints[options.tableHint] && `WITH (${TableHints[options.tableHint]})`
]);
} }
addLimitAndOffset(options, model) { addLimitAndOffset(options, model) {
......
...@@ -50,7 +50,7 @@ module.exports = BaseTypes => { ...@@ -50,7 +50,7 @@ module.exports = BaseTypes => {
class DATE extends BaseTypes.DATE { class DATE extends BaseTypes.DATE {
toSql() { toSql() {
return `DATETIME${this._length ? `(${this._length})` : ''}`; return this._length ? `DATETIME(${this._length})` : 'DATETIME';
} }
_stringify(date, options) { _stringify(date, options) {
date = this._applyTimezone(date, options); date = this._applyTimezone(date, options);
......
...@@ -7,21 +7,23 @@ const util = require('util'); ...@@ -7,21 +7,23 @@ const util = require('util');
const Op = require('../../operators'); const Op = require('../../operators');
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i; const JSON_FUNCTION_REGEX = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i; const JSON_OPERATOR_REGEX = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; const TOKEN_CAPTURE_REGEX = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
const foreignKeyFields = 'CONSTRAINT_NAME as constraint_name,' const FOREIGN_KEY_FIELDS = [
+ 'CONSTRAINT_NAME as constraintName,' 'CONSTRAINT_NAME as constraint_name',
+ 'CONSTRAINT_SCHEMA as constraintSchema,' 'CONSTRAINT_NAME as constraintName',
+ 'CONSTRAINT_SCHEMA as constraintCatalog,' 'CONSTRAINT_SCHEMA as constraintSchema',
+ 'TABLE_NAME as tableName,' 'CONSTRAINT_SCHEMA as constraintCatalog',
+ 'TABLE_SCHEMA as tableSchema,' 'TABLE_NAME as tableName',
+ 'TABLE_SCHEMA as tableCatalog,' 'TABLE_SCHEMA as tableSchema',
+ 'COLUMN_NAME as columnName,' 'TABLE_SCHEMA as tableCatalog',
+ 'REFERENCED_TABLE_SCHEMA as referencedTableSchema,' 'COLUMN_NAME as columnName',
+ 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog,' 'REFERENCED_TABLE_SCHEMA as referencedTableSchema',
+ 'REFERENCED_TABLE_NAME as referencedTableName,' 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog',
+ 'REFERENCED_COLUMN_NAME as referencedColumnName'; 'REFERENCED_TABLE_NAME as referencedTableName',
'REFERENCED_COLUMN_NAME as referencedColumnName'
].join(',');
const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']);
...@@ -41,15 +43,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -41,15 +43,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
collate: null collate: null
}, options || {}); }, options || {});
const database = this.quoteIdentifier(databaseName); return Utils.joinSQLFragments([
const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; 'CREATE DATABASE IF NOT EXISTS',
const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; this.quoteIdentifier(databaseName),
options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`,
return `${`CREATE DATABASE IF NOT EXISTS ${database}${charset}${collate}`.trim()};`; options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`,
';'
]);
} }
dropDatabaseQuery(databaseName) { dropDatabaseQuery(databaseName) {
return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName).trim()};`; return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName)};`;
} }
createSchema() { createSchema() {
...@@ -103,12 +107,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -103,12 +107,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
const table = this.quoteTable(tableName); const table = this.quoteTable(tableName);
let attributesClause = attrStr.join(', '); let attributesClause = attrStr.join(', ');
const comment = options.comment && typeof options.comment === 'string' ? ` COMMENT ${this.escape(options.comment)}` : '';
const engine = options.engine;
const charset = options.charset ? ` DEFAULT CHARSET=${options.charset}` : '';
const collation = options.collate ? ` COLLATE ${options.collate}` : '';
const rowFormat = options.rowFormat ? ` ROW_FORMAT=${options.rowFormat}` : '';
const initialAutoIncrement = options.initialAutoIncrement ? ` AUTO_INCREMENT=${options.initialAutoIncrement}` : '';
const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');
if (options.uniqueKeys) { if (options.uniqueKeys) {
...@@ -132,7 +130,18 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -132,7 +130,18 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
} }
} }
return `CREATE TABLE IF NOT EXISTS ${table} (${attributesClause}) ENGINE=${engine}${comment}${charset}${collation}${initialAutoIncrement}${rowFormat};`; return Utils.joinSQLFragments([
'CREATE TABLE IF NOT EXISTS',
table,
`(${attributesClause})`,
`ENGINE=${options.engine}`,
options.comment && typeof options.comment === 'string' && `COMMENT ${this.escape(options.comment)}`,
options.charset && `DEFAULT CHARSET=${options.charset}`,
options.collate && `COLLATE ${options.collate}`,
options.initialAutoIncrement && `AUTO_INCREMENT=${options.initialAutoIncrement}`,
options.rowFormat && `ROW_FORMAT=${options.rowFormat}`,
';'
]);
} }
...@@ -159,17 +168,28 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -159,17 +168,28 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
} }
addColumnQuery(table, key, dataType) { addColumnQuery(table, key, dataType) {
const definition = this.attributeToSQL(dataType, { return Utils.joinSQLFragments([
context: 'addColumn', 'ALTER TABLE',
tableName: table, this.quoteTable(table),
foreignKey: key 'ADD',
}); this.quoteIdentifier(key),
this.attributeToSQL(dataType, {
return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition};`; context: 'addColumn',
tableName: table,
foreignKey: key
}),
';'
]);
} }
removeColumnQuery(tableName, attributeName) { removeColumnQuery(tableName, attributeName) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(attributeName)};`; return Utils.joinSQLFragments([
'ALTER TABLE',
this.quoteTable(tableName),
'DROP',
this.quoteIdentifier(attributeName),
';'
]);
} }
changeColumnQuery(tableName, attributes) { changeColumnQuery(tableName, attributes) {
...@@ -187,16 +207,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -187,16 +207,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
} }
} }
let finalQuery = ''; return Utils.joinSQLFragments([
if (attrString.length) { 'ALTER TABLE',
finalQuery += `CHANGE ${attrString.join(', ')}`; this.quoteTable(tableName),
finalQuery += constraintString.length ? ' ' : ''; attrString.length && `CHANGE ${attrString.join(', ')}`,
} constraintString.length && `ADD ${constraintString.join(', ')}`,
if (constraintString.length) { ';'
finalQuery += `ADD ${constraintString.join(', ')}`; ]);
}
return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`;
} }
renameColumnQuery(tableName, attrBefore, attributes) { renameColumnQuery(tableName, attrBefore, attributes) {
...@@ -207,7 +224,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -207,7 +224,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`);
} }
return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`; return Utils.joinSQLFragments([
'ALTER TABLE',
this.quoteTable(tableName),
'CHANGE',
attrString.join(', '),
';'
]);
} }
handleSequelizeMethod(smth, tableName, factory, options, prepend) { handleSequelizeMethod(smth, tableName, factory, options, prepend) {
...@@ -300,14 +323,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -300,14 +323,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
} }
showIndexesQuery(tableName, options) { showIndexesQuery(tableName, options) {
return `SHOW INDEX FROM ${this.quoteTable(tableName)}${(options || {}).database ? ` FROM \`${options.database}\`` : ''}`; return Utils.joinSQLFragments([
`SHOW INDEX FROM ${this.quoteTable(tableName)}`,
options && options.database && `FROM \`${options.database}\``
]);
} }
showConstraintsQuery(table, constraintName) { showConstraintsQuery(table, constraintName) {
const tableName = table.tableName || table; const tableName = table.tableName || table;
const schemaName = table.schema; const schemaName = table.schema;
let sql = [ return Utils.joinSQLFragments([
'SELECT CONSTRAINT_CATALOG AS constraintCatalog,', 'SELECT CONSTRAINT_CATALOG AS constraintCatalog,',
'CONSTRAINT_NAME AS constraintName,', 'CONSTRAINT_NAME AS constraintName,',
'CONSTRAINT_SCHEMA AS constraintSchema,', 'CONSTRAINT_SCHEMA AS constraintSchema,',
...@@ -315,18 +341,11 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -315,18 +341,11 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
'TABLE_NAME AS tableName,', 'TABLE_NAME AS tableName,',
'TABLE_SCHEMA AS tableSchema', 'TABLE_SCHEMA AS tableSchema',
'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS', 'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS',
`WHERE table_name='${tableName}'` `WHERE table_name='${tableName}'`,
].join(' '); constraintName && `AND constraint_name = '${constraintName}'`,
schemaName && `AND TABLE_SCHEMA = '${schemaName}'`,
if (constraintName) { ';'
sql += ` AND constraint_name = '${constraintName}'`; ]);
}
if (schemaName) {
sql += ` AND TABLE_SCHEMA = '${schemaName}'`;
}
return `${sql};`;
} }
removeIndexQuery(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
...@@ -336,7 +355,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -336,7 +355,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
} }
return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`; return Utils.joinSQLFragments([
'DROP INDEX',
this.quoteIdentifier(indexName),
'ON',
this.quoteTable(tableName)
]);
} }
attributeToSQL(attribute, options) { attributeToSQL(attribute, options) {
...@@ -444,21 +468,21 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -444,21 +468,21 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
while (currentIndex < stmt.length) { while (currentIndex < stmt.length) {
const string = stmt.substr(currentIndex); const string = stmt.substr(currentIndex);
const functionMatches = jsonFunctionRegex.exec(string); const functionMatches = JSON_FUNCTION_REGEX.exec(string);
if (functionMatches) { if (functionMatches) {
currentIndex += functionMatches[0].indexOf('('); currentIndex += functionMatches[0].indexOf('(');
hasJsonFunction = true; hasJsonFunction = true;
continue; continue;
} }
const operatorMatches = jsonOperatorRegex.exec(string); const operatorMatches = JSON_OPERATOR_REGEX.exec(string);
if (operatorMatches) { if (operatorMatches) {
currentIndex += operatorMatches[0].length; currentIndex += operatorMatches[0].length;
hasJsonFunction = true; hasJsonFunction = true;
continue; continue;
} }
const tokenMatches = tokenCaptureRegex.exec(string); const tokenMatches = TOKEN_CAPTURE_REGEX.exec(string);
if (tokenMatches) { if (tokenMatches) {
const capturedToken = tokenMatches[1]; const capturedToken = tokenMatches[1];
if (capturedToken === '(') { if (capturedToken === '(') {
...@@ -495,7 +519,14 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -495,7 +519,14 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
*/ */
getForeignKeysQuery(table, schemaName) { getForeignKeysQuery(table, schemaName) {
const tableName = table.tableName || table; const tableName = table.tableName || table;
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`; return Utils.joinSQLFragments([
'SELECT',
FOREIGN_KEY_FIELDS,
`FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}'`,
`AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}'`,
'AND REFERENCED_TABLE_NAME IS NOT NULL',
';'
]);
} }
/** /**
...@@ -511,12 +542,25 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -511,12 +542,25 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
const quotedTableName = wrapSingleQuote(table.tableName || table); const quotedTableName = wrapSingleQuote(table.tableName || table);
const quotedColumnName = wrapSingleQuote(columnName); const quotedColumnName = wrapSingleQuote(columnName);
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE` return Utils.joinSQLFragments([
+ ` WHERE (REFERENCED_TABLE_NAME = ${quotedTableName}${table.schema 'SELECT',
? ` AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}` FOREIGN_KEY_FIELDS,
: ''} AND REFERENCED_COLUMN_NAME = ${quotedColumnName})` 'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE',
+ ` OR (TABLE_NAME = ${quotedTableName}${table.schema ? 'WHERE (',
` AND TABLE_SCHEMA = ${quotedSchemaName}` : ''} AND COLUMN_NAME = ${quotedColumnName} AND REFERENCED_TABLE_NAME IS NOT NULL)`; [
`REFERENCED_TABLE_NAME = ${quotedTableName}`,
table.schema && `AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`,
`AND REFERENCED_COLUMN_NAME = ${quotedColumnName}`
],
') OR (',
[
`TABLE_NAME = ${quotedTableName}`,
table.schema && `AND TABLE_SCHEMA = ${quotedSchemaName}`,
`AND COLUMN_NAME = ${quotedColumnName}`,
'AND REFERENCED_TABLE_NAME IS NOT NULL'
],
')'
]);
} }
/** /**
...@@ -528,8 +572,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ...@@ -528,8 +572,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
* @private * @private
*/ */
dropForeignKeyQuery(tableName, foreignKey) { dropForeignKeyQuery(tableName, foreignKey) {
return `ALTER TABLE ${this.quoteTable(tableName)} return Utils.joinSQLFragments([
DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`; 'ALTER TABLE',
this.quoteTable(tableName),
'DROP FOREIGN KEY',
this.quoteIdentifier(foreignKey),
';'
]);
} }
} }
......
...@@ -12,6 +12,7 @@ const operatorsSet = new Set(_.values(operators)); ...@@ -12,6 +12,7 @@ const operatorsSet = new Set(_.values(operators));
let inflection = require('inflection'); let inflection = require('inflection');
exports.classToInvokable = require('./utils/classToInvokable').classToInvokable; exports.classToInvokable = require('./utils/classToInvokable').classToInvokable;
exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments;
exports.Promise = Promise; exports.Promise = Promise;
......
'use strict';
function doesNotWantLeadingSpace(str) {
return /^[;,)]/.test(str);
}
function doesNotWantTrailingSpace(str) {
return /\($/.test(str);
}
/**
* Joins an array of strings with a single space between them,
* except for:
*
* - Strings starting with ';', ',' and ')', which do not get a leading space.
* - Strings ending with '(', which do not get a trailing space.
*
* @param {string[]} parts
* @returns {string}
* @private
*/
function singleSpaceJoinHelper(parts) {
return parts.reduce(({ skipNextLeadingSpace, result }, part) => {
if (skipNextLeadingSpace || doesNotWantLeadingSpace(part)) {
result += part.trim();
} else {
result += ` ${part.trim()}`;
}
return {
skipNextLeadingSpace: doesNotWantTrailingSpace(part),
result
};
}, {
skipNextLeadingSpace: true,
result: ''
}).result;
}
/**
* Joins an array with a single space, auto trimming when needed.
*
* Certain elements do not get leading/trailing spaces.
*
* @param {any[]} array The array to be joined. Falsy values are skipped. If an
* element is another array, this function will be called recursively on that array.
* Otherwise, if a non-string, non-falsy value is present, a TypeError will be thrown.
*
* @returns {string} The joined string.
*
* @private
*/
function joinSQLFragments(array) {
if (array.length === 0) return '';
// Skip falsy fragments
array = array.filter(x => x);
// Resolve recursive calls
array = array.map(fragment => {
if (Array.isArray(fragment)) {
return joinSQLFragments(fragment);
}
return fragment;
});
// Ensure strings
for (const fragment of array) {
if (fragment && typeof fragment !== 'string') {
const error = new TypeError(`Tried to construct a SQL string with a non-string, non-falsy fragment (${fragment}).`);
error.args = array;
error.fragment = fragment;
throw error;
}
}
// Trim fragments
array = array.map(x => x.trim());
// Skip full-whitespace fragments (empty after the above trim)
array = array.filter(x => x !== '');
return singleSpaceJoinHelper(array);
}
exports.joinSQLFragments = joinSQLFragments;
...@@ -71,11 +71,11 @@ if (dialect === 'mariadb') { ...@@ -71,11 +71,11 @@ if (dialect === 'mariadb') {
}, },
{ {
arguments: [{ skip: ['test'] }], arguments: [{ skip: ['test'] }],
expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\');' expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\');'
}, },
{ {
arguments: [{ skip: ['test', 'Te\'st2'] }], arguments: [{ skip: ['test', 'Te\'st2'] }],
expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\',\'Te\\\'st2\');' expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\', \'Te\\\'st2\');'
} }
], ],
......
...@@ -136,12 +136,12 @@ if (current.dialect.name === 'mssql') { ...@@ -136,12 +136,12 @@ if (current.dialect.name === 'mssql') {
// With offset // With offset
expectsql(modifiedGen.selectFromTableFragment({ offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { expectsql(modifiedGen.selectFromTableFragment({ offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), {
mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName'
}); });
// With both limit and offset // With both limit and offset
expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), {
mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName'
}); });
}); });
......
...@@ -56,7 +56,7 @@ if (dialect.startsWith('postgres')) { ...@@ -56,7 +56,7 @@ if (dialect.startsWith('postgres')) {
{ {
title: 'Should use the plus operator', title: 'Should use the plus operator',
arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}],
expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *'
}, },
{ {
title: 'Should use the plus operator with where clause', title: 'Should use the plus operator with where clause',
...@@ -71,12 +71,12 @@ if (dialect.startsWith('postgres')) { ...@@ -71,12 +71,12 @@ if (dialect.startsWith('postgres')) {
{ {
title: 'Should use the minus operator', title: 'Should use the minus operator',
arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}],
expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *'
}, },
{ {
title: 'Should use the minus operator with negative value', title: 'Should use the minus operator with negative value',
arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}],
expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *'
}, },
{ {
title: 'Should use the minus operator with where clause', title: 'Should use the minus operator with where clause',
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!