{ "version": 3, "sources": ["../../../src/dialects/abstract/query-generator.js"], "sourcesContent": ["'use strict';\n\nconst util = require('util');\nconst _ = require('lodash');\nconst uuidv4 = require('uuid').v4;\n\nconst Utils = require('../../utils');\nconst deprecations = require('../../utils/deprecations');\nconst SqlString = require('../../sql-string');\nconst DataTypes = require('../../data-types');\nconst Model = require('../../model');\nconst Association = require('../../associations/base');\nconst BelongsTo = require('../../associations/belongs-to');\nconst BelongsToMany = require('../../associations/belongs-to-many');\nconst HasMany = require('../../associations/has-many');\nconst Op = require('../../operators');\nconst sequelizeError = require('../../errors');\nconst IndexHints = require('../../index-hints');\n\n\n/**\n * Abstract Query Generator\n *\n * @private\n */\nclass QueryGenerator {\n constructor(options) {\n if (!options.sequelize) throw new Error('QueryGenerator initialized without options.sequelize');\n if (!options._dialect) throw new Error('QueryGenerator initialized without options._dialect');\n\n this.sequelize = options.sequelize;\n this.options = options.sequelize.options;\n\n // dialect name\n this.dialect = options._dialect.name;\n this._dialect = options._dialect;\n\n // wrap quoteIdentifier with common logic\n this._initQuoteIdentifier();\n }\n\n extractTableDetails(tableName, options) {\n options = options || {};\n tableName = tableName || {};\n return {\n schema: tableName.schema || options.schema || 'public',\n tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName,\n delimiter: tableName.delimiter || options.delimiter || '.'\n };\n }\n\n addSchema(param) {\n if (!param._schema) return param.tableName || param;\n const self = this;\n return {\n tableName: param.tableName || param,\n table: param.tableName || param,\n name: param.name || param,\n schema: param._schema,\n delimiter: param._schemaDelimiter || '.',\n toString() {\n return self.quoteTable(this);\n }\n };\n }\n\n dropSchema(tableName, options) {\n return this.dropTableQuery(tableName, options);\n }\n\n describeTableQuery(tableName, schema, schemaDelimiter) {\n const table = this.quoteTable(\n this.addSchema({\n tableName,\n _schema: schema,\n _schemaDelimiter: schemaDelimiter\n })\n );\n\n return `DESCRIBE ${table};`;\n }\n\n dropTableQuery(tableName) {\n return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)};`;\n }\n\n renameTableQuery(before, after) {\n return `ALTER TABLE ${this.quoteTable(before)} RENAME TO ${this.quoteTable(after)};`;\n }\n\n /**\n * Returns an insert into command\n *\n * @param {string} table\n * @param {object} valueHash attribute value pairs\n * @param {object} modelAttributes\n * @param {object} [options]\n *\n * @private\n */\n insertQuery(table, valueHash, modelAttributes, options) {\n options = options || {};\n _.defaults(options, this.options);\n\n const modelAttributeMap = {};\n const bind = [];\n const fields = [];\n const returningModelAttributes = [];\n const values = [];\n const quotedTable = this.quoteTable(table);\n const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;\n let query;\n let valueQuery = '';\n let emptyQuery = '';\n let outputFragment = '';\n let returningFragment = '';\n let identityWrapperRequired = false;\n let tmpTable = ''; //tmpTable declaration for trigger\n\n if (modelAttributes) {\n _.each(modelAttributes, (attribute, key) => {\n modelAttributeMap[key] = attribute;\n if (attribute.field) {\n modelAttributeMap[attribute.field] = attribute;\n }\n });\n }\n\n if (this._dialect.supports['DEFAULT VALUES']) {\n emptyQuery += ' DEFAULT VALUES';\n } else if (this._dialect.supports['VALUES ()']) {\n emptyQuery += ' VALUES ()';\n }\n\n if (this._dialect.supports.returnValues && options.returning) {\n const returnValues = this.generateReturnValues(modelAttributes, options);\n\n returningModelAttributes.push(...returnValues.returnFields);\n returningFragment = returnValues.returningFragment;\n tmpTable = returnValues.tmpTable || '';\n outputFragment = returnValues.outputFragment || '';\n }\n\n if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {\n // Not currently supported with search path (requires output of multiple queries)\n options.bindParam = false;\n }\n\n if (this._dialect.supports.EXCEPTION && options.exception) {\n // Not currently supported with bind parameters (requires output of multiple queries)\n options.bindParam = false;\n }\n\n valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull);\n for (const key in valueHash) {\n if (Object.prototype.hasOwnProperty.call(valueHash, key)) {\n const value = valueHash[key];\n fields.push(this.quoteIdentifier(key));\n\n // SERIALS' can't be NULL in postgresql, use DEFAULT where supported\n if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && value == null) {\n if (!this._dialect.supports.autoIncrement.defaultValue) {\n fields.splice(-1, 1);\n } else if (this._dialect.supports.DEFAULT) {\n values.push('DEFAULT');\n } else {\n values.push(this.escape(null));\n }\n } else {\n if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true) {\n identityWrapperRequired = true;\n }\n\n if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {\n values.push(this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }));\n } else {\n values.push(this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }, bindParam));\n }\n }\n }\n }\n\n let onDuplicateKeyUpdate = '';\n\n // `options.updateOnDuplicate` is the list of field names to update if a duplicate key is hit during the insert. It\n // contains just the field names. This option is _usually_ explicitly set by the corresponding query-interface\n // upsert function.\n if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) {\n if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite\n // If no conflict target columns were specified, use the primary key names from options.upsertKeys\n const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));\n const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`);\n onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')})`;\n // if update keys are provided, then apply them here. if there are no updateKeys provided, then do not try to\n // do an update. Instead, fall back to DO NOTHING.\n onDuplicateKeyUpdate += _.isEmpty(updateKeys) ? ' DO NOTHING ' : ` DO UPDATE SET ${updateKeys.join(',')}`;\n } else {\n const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`);\n // the rough equivalent to ON CONFLICT DO NOTHING in mysql, etc is ON DUPLICATE KEY UPDATE id = id\n // So, if no update values were provided, fall back to the identifier columns provided in the upsertKeys array.\n // This will be the primary key in most cases, but it could be some other constraint.\n if (_.isEmpty(valueKeys) && options.upsertKeys) {\n valueKeys.push(...options.upsertKeys.map(attr => `${this.quoteIdentifier(attr)}=${this.quoteIdentifier(attr)}`));\n }\n\n // edge case... but if for some reason there were no valueKeys, and there were also no upsertKeys... then we\n // can no longer build the requested query without a syntax error. Let's throw something more graceful here\n // so the devs know what the problem is.\n if (_.isEmpty(valueKeys)) {\n throw new Error('No update values found for ON DUPLICATE KEY UPDATE clause, and no identifier fields could be found to use instead.');\n }\n onDuplicateKeyUpdate += `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`;\n }\n }\n\n const replacements = {\n ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : '',\n onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '',\n attributes: fields.join(','),\n output: outputFragment,\n values: values.join(','),\n tmpTable\n };\n\n valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`;\n emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`;\n\n // Mostly for internal use, so we expect the user to know what he's doing!\n // pg_temp functions are private per connection, so we never risk this function interfering with another one.\n if (this._dialect.supports.EXCEPTION && options.exception) {\n const dropFunction = 'DROP FUNCTION IF EXISTS pg_temp.testfunc()';\n\n if (returningModelAttributes.length === 0) {\n returningModelAttributes.push('*');\n }\n\n const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`;\n const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`;\n\n options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;';\n valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter} BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`;\n } else {\n valueQuery += returningFragment;\n emptyQuery += returningFragment;\n }\n\n query = `${replacements.attributes.length ? valueQuery : emptyQuery};`;\n if (this._dialect.supports.finalTable) {\n query = `SELECT * FROM FINAL TABLE(${ replacements.attributes.length ? valueQuery : emptyQuery });`;\n }\n if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) {\n query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`;\n }\n\n // Used by Postgres upsertQuery and calls to here with options.exception set to true\n const result = { query };\n if (options.bindParam !== false) {\n result.bind = bind;\n }\n\n return result;\n }\n\n /**\n * Returns an insert into command for multiple values.\n *\n * @param {string} tableName\n * @param {object} fieldValueHashes\n * @param {object} options\n * @param {object} fieldMappedAttributes\n *\n * @private\n */\n bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) {\n options = options || {};\n fieldMappedAttributes = fieldMappedAttributes || {};\n\n const tuples = [];\n const serials = {};\n const allAttributes = [];\n let onDuplicateKeyUpdate = '';\n\n for (const fieldValueHash of fieldValueHashes) {\n _.forOwn(fieldValueHash, (value, key) => {\n if (!allAttributes.includes(key)) {\n allAttributes.push(key);\n }\n if (\n fieldMappedAttributes[key]\n && fieldMappedAttributes[key].autoIncrement === true\n ) {\n serials[key] = true;\n }\n });\n }\n\n for (const fieldValueHash of fieldValueHashes) {\n const values = allAttributes.map(key => {\n if (\n this._dialect.supports.bulkDefault\n && serials[key] === true\n ) {\n // fieldValueHashes[key] ?? 'DEFAULT'\n return fieldValueHash[key] != null ? fieldValueHash[key] : 'DEFAULT';\n }\n\n return this.escape(fieldValueHash[key], fieldMappedAttributes[key], { context: 'INSERT' });\n });\n\n tuples.push(`(${values.join(',')})`);\n }\n\n // `options.updateOnDuplicate` is the list of field names to update if a duplicate key is hit during the insert. It\n // contains just the field names. This option is _usually_ explicitly set by the corresponding query-interface\n // upsert function.\n if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) {\n if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite\n // If no conflict target columns were specified, use the primary key names from options.upsertKeys\n const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));\n const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`);\n onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`;\n } else { // mysql / maria\n const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`);\n onDuplicateKeyUpdate = `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`;\n }\n }\n\n const ignoreDuplicates = options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : '';\n const attributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(',');\n const onConflictDoNothing = options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '';\n let returning = '';\n\n if (this._dialect.supports.returnValues && options.returning) {\n const returnValues = this.generateReturnValues(fieldMappedAttributes, options);\n\n returning += returnValues.returningFragment;\n }\n\n return Utils.joinSQLFragments([\n 'INSERT',\n ignoreDuplicates,\n 'INTO',\n this.quoteTable(tableName),\n `(${attributes})`,\n 'VALUES',\n tuples.join(','),\n onDuplicateKeyUpdate,\n onConflictDoNothing,\n returning,\n ';'\n ]);\n }\n\n /**\n * Returns an update query\n *\n * @param {string} tableName\n * @param {object} attrValueHash\n * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer\n * @param {object} options\n * @param {object} attributes\n *\n * @private\n */\n updateQuery(tableName, attrValueHash, where, options, attributes) {\n options = options || {};\n _.defaults(options, this.options);\n\n attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);\n\n const values = [];\n const bind = [];\n const modelAttributeMap = {};\n let outputFragment = '';\n let tmpTable = ''; // tmpTable declaration for trigger\n let suffix = '';\n\n if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {\n // Not currently supported with search path (requires output of multiple queries)\n options.bindParam = false;\n }\n\n const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;\n\n if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) {\n if (this.dialect !== 'mssql' && this.dialect !== 'db2') {\n suffix = ` LIMIT ${this.escape(options.limit)} `;\n }\n }\n\n if (this._dialect.supports.returnValues && options.returning) {\n const returnValues = this.generateReturnValues(attributes, options);\n\n suffix += returnValues.returningFragment;\n tmpTable = returnValues.tmpTable || '';\n outputFragment = returnValues.outputFragment || '';\n\n // ensure that the return output is properly mapped to model fields.\n if (!this._dialect.supports.returnValues.output && options.returning) {\n options.mapToModel = true;\n }\n }\n\n if (attributes) {\n _.each(attributes, (attribute, key) => {\n modelAttributeMap[key] = attribute;\n if (attribute.field) {\n modelAttributeMap[attribute.field] = attribute;\n }\n });\n }\n\n for (const key in attrValueHash) {\n if (modelAttributeMap && modelAttributeMap[key] &&\n modelAttributeMap[key].autoIncrement === true &&\n !this._dialect.supports.autoIncrement.update) {\n // not allowed to update identity column\n continue;\n }\n\n const value = attrValueHash[key];\n\n if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {\n values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })}`);\n } else {\n values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }, bindParam)}`);\n }\n }\n\n const whereOptions = { ...options, bindParam };\n\n if (values.length === 0) {\n return '';\n }\n\n const query = `${tmpTable}UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where, whereOptions)}${suffix}`.trim();\n // Used by Postgres upsertQuery and calls to here with options.exception set to true\n const result = { query };\n if (options.bindParam !== false) {\n result.bind = bind;\n }\n return result;\n }\n\n /**\n * Returns an update query using arithmetic operator\n *\n * @param {string} operator String with the arithmetic operator (e.g. '+' or '-')\n * @param {string} tableName Name of the table\n * @param {object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer\n * @param {object} incrementAmountsByField A plain-object with attribute-value-pairs\n * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs\n * @param {object} options\n *\n * @private\n */\n arithmeticQuery(operator, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) {\n options = options || {};\n _.defaults(options, { returning: true });\n\n extraAttributesToBeUpdated = Utils.removeNullValuesFromHash(extraAttributesToBeUpdated, this.options.omitNull);\n\n let outputFragment = '';\n let returningFragment = '';\n\n if (this._dialect.supports.returnValues && options.returning) {\n const returnValues = this.generateReturnValues(null, options);\n\n outputFragment = returnValues.outputFragment;\n returningFragment = returnValues.returningFragment;\n }\n\n const updateSetSqlFragments = [];\n for (const field in incrementAmountsByField) {\n const incrementAmount = incrementAmountsByField[field];\n const quotedField = this.quoteIdentifier(field);\n const escapedAmount = this.escape(incrementAmount);\n updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`);\n }\n for (const field in extraAttributesToBeUpdated) {\n const newValue = extraAttributesToBeUpdated[field];\n const quotedField = this.quoteIdentifier(field);\n const escapedValue = this.escape(newValue);\n updateSetSqlFragments.push(`${quotedField}=${escapedValue}`);\n }\n\n return Utils.joinSQLFragments([\n 'UPDATE',\n this.quoteTable(tableName),\n 'SET',\n updateSetSqlFragments.join(','),\n outputFragment,\n this.whereQuery(where),\n returningFragment\n ]);\n }\n\n /*\n Returns an add index query.\n Parameters:\n - tableName -> Name of an existing table, possibly with schema.\n - options:\n - type: UNIQUE|FULLTEXT|SPATIAL\n - name: The name of the index. Default is