UNPKG

sequelize

Version:

Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift and Snowflake’s Data Cloud. It features solid transaction support, relations, eager and lazy loading, read replication and more.

1,156 lines (1,154 loc) 90.9 kB
"use strict"; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); const util = require("util"); const _ = require("lodash"); const uuidv4 = require("uuid").v4; const Utils = require("../../utils"); const deprecations = require("../../utils/deprecations"); 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 sequelizeError = require("../../errors"); const IndexHints = require("../../index-hints"); 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; this.dialect = options._dialect.name; this._dialect = options._dialect; this._initQuoteIdentifier(); } extractTableDetails(tableName, options) { options = options || {}; tableName = tableName || {}; return { schema: tableName.schema || options.schema || this.options.schema || "public", tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName, delimiter: tableName.delimiter || options.delimiter || "." }; } addSchema(param) { if (!param._schema) return param.tableName || param; const self = this; return { tableName: param.tableName || param, table: param.tableName || param, name: param.name || param, schema: param._schema, delimiter: param._schemaDelimiter || ".", toString() { return self.quoteTable(this); } }; } dropSchema(tableName, options) { return this.dropTableQuery(tableName, options); } describeTableQuery(tableName, schema, schemaDelimiter) { const table = this.quoteTable(this.addSchema({ tableName, _schema: schema, _schemaDelimiter: schemaDelimiter })); 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)};`; } populateInsertQueryReturnIntoBinds() { } insertQuery(table, valueHash, modelAttributes, options) { options = options || {}; _.defaults(options, this.options); const modelAttributeMap = {}; const bind = options.bind || []; const fields = []; const returningModelAttributes = []; const returnTypes = []; const values = []; const quotedTable = this.quoteTable(table); const bindParam = options.bindParam === void 0 ? this.bindParam(bind) : options.bindParam; const returnAttributes = []; let query; let valueQuery = ""; let emptyQuery = ""; let outputFragment = ""; let returningFragment = ""; let identityWrapperRequired = false; let tmpTable = ""; if (modelAttributes) { _.each(modelAttributes, (attribute, key) => { modelAttributeMap[key] = attribute; if (attribute.field) { modelAttributeMap[attribute.field] = attribute; } }); } if (this._dialect.supports["DEFAULT VALUES"]) { emptyQuery += " DEFAULT VALUES"; } else if (this._dialect.supports["VALUES ()"]) { emptyQuery += " VALUES ()"; } if ((this._dialect.supports.returnValues || this._dialect.supports.returnIntoValues) && options.returning) { const returnValues = this.generateReturnValues(modelAttributes, options); returningModelAttributes.push(...returnValues.returnFields); if (this._dialect.supports.returnIntoValues) { returnTypes.push(...returnValues.returnTypes); } returningFragment = returnValues.returningFragment; tmpTable = returnValues.tmpTable || ""; outputFragment = returnValues.outputFragment || ""; } if (_.get(this, ["sequelize", "options", "dialectOptions", "prependSearchPath"]) || options.searchPath) { options.bindParam = false; } if (this._dialect.supports.EXCEPTION && options.exception) { options.bindParam = false; } valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull); for (const key in valueHash) { if (Object.prototype.hasOwnProperty.call(valueHash, key)) { const value = valueHash[key]; fields.push(this.quoteIdentifier(key)); if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && value == null) { if (!this._dialect.supports.autoIncrement.defaultValue) { fields.splice(-1, 1); } else if (this._dialect.supports.DEFAULT) { values.push("DEFAULT"); } else { values.push(this.escape(null)); } } else { if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true) { identityWrapperRequired = true; } if (value instanceof Utils.SequelizeMethod || options.bindParam === false) { values.push(this.escape(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "INSERT" })); } else { values.push(this.format(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "INSERT" }, bindParam)); } } } } let onDuplicateKeyUpdate = ""; if (!_.isEmpty(options.conflictWhere) && !this._dialect.supports.inserts.onConflictWhere) { throw new Error("missing dialect support for conflictWhere option"); } if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) { if (this._dialect.supports.inserts.updateOnDuplicate == " ON CONFLICT DO UPDATE SET") { const conflictKeys = options.upsertKeys.map((attr) => this.quoteIdentifier(attr)); const updateKeys = options.updateOnDuplicate.map((attr) => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`); const fragments = [ "ON CONFLICT", "(", conflictKeys.join(","), ")" ]; if (!_.isEmpty(options.conflictWhere)) { fragments.push(this.whereQuery(options.conflictWhere, options)); } if (_.isEmpty(updateKeys)) { fragments.push("DO NOTHING"); } else { fragments.push("DO UPDATE SET", updateKeys.join(",")); } onDuplicateKeyUpdate = ` ${Utils.joinSQLFragments(fragments)}`; } else { const valueKeys = options.updateOnDuplicate.map((attr) => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`); if (_.isEmpty(valueKeys) && options.upsertKeys) { valueKeys.push(...options.upsertKeys.map((attr) => `${this.quoteIdentifier(attr)}=${this.quoteIdentifier(attr)}`)); } if (_.isEmpty(valueKeys)) { throw new Error("No update values found for ON DUPLICATE KEY UPDATE clause, and no identifier fields could be found to use instead."); } onDuplicateKeyUpdate += `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(",")}`; } } const replacements = { ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : "", onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : "", attributes: fields.join(","), output: outputFragment, values: values.join(","), tmpTable }; valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`; emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`; if (this._dialect.supports.EXCEPTION && options.exception) { const dropFunction = "DROP FUNCTION IF EXISTS pg_temp.testfunc()"; if (returningModelAttributes.length === 0) { returningModelAttributes.push("*"); } const delimiter = `$func_${uuidv4().replace(/-/g, "")}$`; const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(", (testfunc.response).")}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; options.exception = "WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;"; 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}`; } else { valueQuery += returningFragment; emptyQuery += returningFragment; } if (this._dialect.supports.returnIntoValues && options.returning) { this.populateInsertQueryReturnIntoBinds(returningModelAttributes, returnTypes, bind.length, returnAttributes, options); } query = `${replacements.attributes.length ? valueQuery : emptyQuery}${returnAttributes.join(",")};`; if (this._dialect.supports.finalTable) { query = `SELECT * FROM FINAL TABLE(${replacements.attributes.length ? valueQuery : emptyQuery});`; } if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) { query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`; } const result = { query }; if (options.bindParam !== false) { result.bind = bind; } return result; } bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { options = options || {}; fieldMappedAttributes = fieldMappedAttributes || {}; const tuples = []; const serials = {}; const allAttributes = []; let onDuplicateKeyUpdate = ""; for (const fieldValueHash of fieldValueHashes) { _.forOwn(fieldValueHash, (value, key) => { if (!allAttributes.includes(key)) { allAttributes.push(key); } if (fieldMappedAttributes[key] && fieldMappedAttributes[key].autoIncrement === true) { serials[key] = true; } }); } for (const fieldValueHash of fieldValueHashes) { const values = allAttributes.map((key) => { if (this._dialect.supports.bulkDefault && serials[key] === true) { return fieldValueHash[key] != null ? fieldValueHash[key] : "DEFAULT"; } return this.escape(fieldValueHash[key], fieldMappedAttributes[key], { context: "INSERT" }); }); tuples.push(`(${values.join(",")})`); } if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) { if (this._dialect.supports.inserts.updateOnDuplicate == " ON CONFLICT DO UPDATE SET") { const conflictKeys = options.upsertKeys.map((attr) => this.quoteIdentifier(attr)); const updateKeys = options.updateOnDuplicate.map((attr) => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`); let whereClause = false; if (options.conflictWhere) { if (!this._dialect.supports.inserts.onConflictWhere) { throw new Error(`conflictWhere not supported for dialect ${this._dialect.name}`); } whereClause = this.whereQuery(options.conflictWhere, options); } onDuplicateKeyUpdate = [ "ON CONFLICT", "(", conflictKeys.join(","), ")", whereClause, "DO UPDATE SET", updateKeys.join(",") ]; } else { if (options.conflictWhere) { throw new Error(`conflictWhere not supported for dialect ${this._dialect.name}`); } const valueKeys = options.updateOnDuplicate.map((attr) => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`); onDuplicateKeyUpdate = `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(",")}`; } } const ignoreDuplicates = options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : ""; const attributes = allAttributes.map((attr) => this.quoteIdentifier(attr)).join(","); const onConflictDoNothing = options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : ""; let returning = ""; if (this._dialect.supports.returnValues && options.returning) { const returnValues = this.generateReturnValues(fieldMappedAttributes, options); returning += returnValues.returningFragment; } return Utils.joinSQLFragments([ "INSERT", ignoreDuplicates, "INTO", this.quoteTable(tableName), `(${attributes})`, "VALUES", tuples.join(","), onDuplicateKeyUpdate, onConflictDoNothing, returning, ";" ]); } updateQuery(tableName, attrValueHash, where, options, attributes) { options = options || {}; _.defaults(options, this.options); attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); const values = []; const bind = []; const modelAttributeMap = {}; let outputFragment = ""; let tmpTable = ""; let suffix = ""; if (_.get(this, ["sequelize", "options", "dialectOptions", "prependSearchPath"]) || options.searchPath) { options.bindParam = false; } const bindParam = options.bindParam === void 0 ? this.bindParam(bind) : options.bindParam; if (this._dialect.supports["LIMIT ON UPDATE"] && options.limit) { if (!["mssql", "db2", "oracle"].includes(this.dialect)) { suffix = ` LIMIT ${this.escape(options.limit)} `; } else if (this.dialect === "oracle") { if (where && (where.length && where.length > 0 || Object.keys(where).length > 0)) { suffix += " AND "; } else { suffix += " WHERE "; } suffix += `rownum <= ${this.escape(options.limit)} `; } } if (this._dialect.supports.returnValues && options.returning) { const returnValues = this.generateReturnValues(attributes, options); suffix += returnValues.returningFragment; tmpTable = returnValues.tmpTable || ""; outputFragment = returnValues.outputFragment || ""; if (!this._dialect.supports.returnValues.output && options.returning) { options.mapToModel = true; } } if (attributes) { _.each(attributes, (attribute, key) => { modelAttributeMap[key] = attribute; if (attribute.field) { modelAttributeMap[attribute.field] = attribute; } }); } for (const key in attrValueHash) { if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && !this._dialect.supports.autoIncrement.update) { continue; } const value = attrValueHash[key]; if (value instanceof Utils.SequelizeMethod || options.bindParam === false) { values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "UPDATE" })}`); } else { values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "UPDATE" }, bindParam)}`); } } const whereOptions = __spreadProps(__spreadValues({}, options), { bindParam }); if (values.length === 0) { return ""; } const query = `${tmpTable}UPDATE ${this.quoteTable(tableName)} SET ${values.join(",")}${outputFragment} ${this.whereQuery(where, whereOptions)}${suffix}`.trim(); const result = { query }; if (options.bindParam !== false) { result.bind = bind; } return result; } arithmeticQuery(operator, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = options || {}; _.defaults(options, { returning: true }); extraAttributesToBeUpdated = Utils.removeNullValuesFromHash(extraAttributesToBeUpdated, this.options.omitNull); let outputFragment = ""; let returningFragment = ""; if (this._dialect.supports.returnValues && options.returning) { const returnValues = this.generateReturnValues(null, options); outputFragment = returnValues.outputFragment; returningFragment = returnValues.returningFragment; } const updateSetSqlFragments = []; for (const field in incrementAmountsByField) { const incrementAmount = incrementAmountsByField[field]; const quotedField = this.quoteIdentifier(field); const escapedAmount = this.escape(incrementAmount); updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`); } for (const field in extraAttributesToBeUpdated) { const newValue = extraAttributesToBeUpdated[field]; const quotedField = this.quoteIdentifier(field); const escapedValue = this.escape(newValue); updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); } return Utils.joinSQLFragments([ "UPDATE", this.quoteTable(tableName), "SET", updateSetSqlFragments.join(","), outputFragment, this.whereQuery(where), returningFragment ]); } addIndexQuery(tableName, attributes, options, rawTablename) { options = options || {}; if (!Array.isArray(attributes)) { options = attributes; attributes = void 0; } else { options.fields = attributes; } options.prefix = options.prefix || rawTablename || tableName; if (options.prefix && typeof options.prefix === "string") { options.prefix = options.prefix.replace(/\./g, "_"); options.prefix = options.prefix.replace(/("|')/g, ""); } const fieldsSql = options.fields.map((field) => { if (field instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(field); } if (typeof field === "string") { field = { name: field }; } let result = ""; if (field.attribute) { field.name = field.attribute; } if (!field.name) { throw new Error(`The following index field has no name: ${util.inspect(field)}`); } result += this.quoteIdentifier(field.name); if (this._dialect.supports.index.collate && field.collate) { result += ` COLLATE ${this.quoteIdentifier(field.collate)}`; } if (this._dialect.supports.index.operator) { const operator = field.operator || options.operator; if (operator) { result += ` ${operator}`; } } if (this._dialect.supports.index.length && field.length) { result += `(${field.length})`; } if (field.order) { result += ` ${field.order}`; } return result; }); if (!options.name) { options = Utils.nameIndex(options, options.prefix); } options = Model._conformIndex(options); if (!this._dialect.supports.index.type) { delete options.type; } if (options.where) { options.where = this.whereQuery(options.where); } if (typeof tableName === "string") { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } const concurrently = this._dialect.supports.index.concurrently && options.concurrently ? "CONCURRENTLY" : void 0; let ind; if (this._dialect.supports.indexViaAlter) { ind = [ "ALTER TABLE", tableName, concurrently, "ADD" ]; } else { ind = ["CREATE"]; } ind = ind.concat(options.unique ? "UNIQUE" : "", options.type, "INDEX", !this._dialect.supports.indexViaAlter ? concurrently : void 0, this.quoteIdentifiers(options.name), this._dialect.supports.index.using === 1 && options.using ? `USING ${options.using}` : "", !this._dialect.supports.indexViaAlter ? `ON ${tableName}` : void 0, this._dialect.supports.index.using === 2 && options.using ? `USING ${options.using}` : "", `(${fieldsSql.join(", ")})`, this._dialect.supports.index.parser && options.parser ? `WITH PARSER ${options.parser}` : void 0, this._dialect.supports.index.where && options.where ? options.where : void 0); return _.compact(ind).join(" "); } addConstraintQuery(tableName, options) { if (typeof tableName === "string") { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } return Utils.joinSQLFragments([ "ALTER TABLE", tableName, "ADD", this.getConstraintSnippet(tableName, options || {}), ";" ]); } getConstraintSnippet(tableName, options) { let constraintSnippet, constraintName; const fieldsSql = options.fields.map((field) => { if (typeof field === "string") { return this.quoteIdentifier(field); } if (field instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(field); } if (field.attribute) { field.name = field.attribute; } if (!field.name) { throw new Error(`The following index field has no name: ${field}`); } return this.quoteIdentifier(field.name); }); const fieldsSqlQuotedString = fieldsSql.join(", "); const fieldsSqlString = fieldsSql.join("_"); switch (options.type.toUpperCase()) { case "UNIQUE": constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_uk`); constraintSnippet = `CONSTRAINT ${constraintName} UNIQUE (${fieldsSqlQuotedString})`; break; case "CHECK": options.where = this.whereItemsQuery(options.where); constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_ck`); constraintSnippet = `CONSTRAINT ${constraintName} CHECK (${options.where})`; break; case "DEFAULT": if (options.defaultValue === void 0) { throw new Error("Default value must be specified for DEFAULT CONSTRAINT"); } if (this._dialect.name !== "mssql") { throw new Error("Default constraints are supported only for MSSQL dialect."); } constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_df`); constraintSnippet = `CONSTRAINT ${constraintName} DEFAULT (${this.escape(options.defaultValue)}) FOR ${fieldsSql[0]}`; break; case "PRIMARY KEY": constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_pk`); constraintSnippet = `CONSTRAINT ${constraintName} PRIMARY KEY (${fieldsSqlQuotedString})`; break; case "FOREIGN KEY": const references = options.references; if (!references || !references.table || !(references.field || references.fields)) { throw new Error("references object with table and field must be specified"); } constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`); const quotedReferences = typeof references.field !== "undefined" ? this.quoteIdentifier(references.field) : references.fields.map((f) => this.quoteIdentifier(f)).join(", "); const referencesSnippet = `${this.quoteTable(references.table)} (${quotedReferences})`; constraintSnippet = `CONSTRAINT ${constraintName} `; constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`; if (options.onUpdate) { constraintSnippet += ` ON UPDATE ${options.onUpdate.toUpperCase()}`; } if (options.onDelete) { constraintSnippet += ` ON DELETE ${options.onDelete.toUpperCase()}`; } break; default: throw new Error(`${options.type} is invalid.`); } if (options.deferrable && ["UNIQUE", "PRIMARY KEY", "FOREIGN KEY"].includes(options.type.toUpperCase())) { constraintSnippet += ` ${this.deferConstraintsQuery(options)}`; } return constraintSnippet; } removeConstraintQuery(tableName, constraintName) { if (typeof tableName === "string") { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } return Utils.joinSQLFragments([ "ALTER TABLE", tableName, "DROP CONSTRAINT", this.quoteIdentifiers(constraintName) ]); } quote(collection, parent, connector) { const validOrderOptions = [ "ASC", "DESC", "ASC NULLS LAST", "DESC NULLS LAST", "ASC NULLS FIRST", "DESC NULLS FIRST", "NULLS FIRST", "NULLS LAST" ]; connector = connector || "."; if (typeof collection === "string") { return this.quoteIdentifiers(collection); } if (Array.isArray(collection)) { collection.forEach((item2, index) => { const previous = collection[index - 1]; let previousAssociation; let previousModel; if (!previous && parent !== void 0) { previousModel = parent; } else if (previous && previous instanceof Association) { previousAssociation = previous; previousModel = previous.target; } if (previousModel && previousModel.prototype instanceof Model) { let model; let as; if (typeof item2 === "function" && item2.prototype instanceof Model) { model = item2; } else if (_.isPlainObject(item2) && item2.model && item2.model.prototype instanceof Model) { model = item2.model; as = item2.as; } if (model) { if (!as && previousAssociation && previousAssociation instanceof Association && previousAssociation.through && previousAssociation.through.model === model) { item2 = new Association(previousModel, model, { as: model.name }); } else { item2 = previousModel.getAssociationForAlias(model, as); if (!item2) { item2 = previousModel.getAssociationForAlias(model, model.name); } } if (!(item2 instanceof Association)) { throw new Error(util.format("Unable to find a valid association for model, '%s'", model.name)); } } } if (typeof item2 === "string") { const orderIndex = validOrderOptions.indexOf(item2.toUpperCase()); if (index > 0 && orderIndex !== -1) { item2 = this.sequelize.literal(` ${validOrderOptions[orderIndex]}`); } else if (previousModel && previousModel.prototype instanceof Model) { if (previousModel.associations !== void 0 && previousModel.associations[item2]) { item2 = previousModel.associations[item2]; } else if (previousModel.rawAttributes !== void 0 && previousModel.rawAttributes[item2] && item2 !== previousModel.rawAttributes[item2].field) { item2 = previousModel.rawAttributes[item2].field; } else if (item2.includes(".") && previousModel.rawAttributes !== void 0) { const itemSplit = item2.split("."); if (previousModel.rawAttributes[itemSplit[0]].type instanceof DataTypes.JSON) { const identifier = this.quoteIdentifiers(`${previousModel.name}.${previousModel.rawAttributes[itemSplit[0]].field}`); const path = itemSplit.slice(1); item2 = this.jsonPathExtractionQuery(identifier, path); item2 = this.sequelize.literal(item2); } } } } collection[index] = item2; }, this); const collectionLength = collection.length; const tableNames = []; let item; let i = 0; for (i = 0; i < collectionLength - 1; i++) { item = collection[i]; if (typeof item === "string" || item._modelAttribute || item instanceof Utils.SequelizeMethod) { break; } else if (item instanceof Association) { tableNames[i] = item.as; } } let sql = ""; if (i > 0) { sql += `${this.quoteIdentifier(tableNames.join(connector))}.`; } else if (typeof collection[0] === "string" && parent) { sql += `${this.quoteIdentifier(parent.name)}.`; } collection.slice(i).forEach((collectionItem) => { sql += this.quote(collectionItem, parent, connector); }, this); return sql; } if (collection._modelAttribute) { return `${this.quoteTable(collection.Model.name)}.${this.quoteIdentifier(collection.fieldName)}`; } if (collection instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(collection); } if (_.isPlainObject(collection) && collection.raw) { throw new Error('The `{raw: "..."}` syntax is no longer supported. Use `sequelize.literal` instead.'); } throw new Error(`Unknown structure passed to order / group: ${util.inspect(collection)}`); } _initQuoteIdentifier() { this._quoteIdentifier = this.quoteIdentifier; this.quoteIdentifier = function(identifier, force) { if (identifier === "*") return identifier; return this._quoteIdentifier(identifier, force); }; } quoteIdentifier(identifier, force) { throw new Error(`quoteIdentifier for Dialect "${this.dialect}" is not implemented`); } quoteIdentifiers(identifiers) { if (identifiers.includes(".")) { identifiers = identifiers.split("."); 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); } quoteAttribute(attribute, model) { if (model && attribute in model.rawAttributes) { return this.quoteIdentifier(attribute); } return this.quoteIdentifiers(attribute); } getAliasToken() { return "AS"; } 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 { table = this.quoteIdentifier(param); } if (alias) { table += ` ${this.getAliasToken()} ${this.quoteIdentifier(alias)}`; } return table; } escape(value, field, options) { options = options || {}; if (value !== null && value !== void 0) { if (value instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(value); } if (field && field.type) { if (field.type instanceof DataTypes.STRING && ["mysql", "mariadb"].includes(this.dialect) && ["number", "boolean"].includes(typeof value)) { value = String(Number(value)); } this.validate(value, field, options); if (field.type.stringify) { const simpleEscape = (escVal) => SqlString.escape(escVal, this.options.timezone, this.dialect); value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone, operation: options.operation }); if (field.type.escape === false) { return value; } } } } return SqlString.escape(value, this.options.timezone, this.dialect); } bindParam(bind) { return (value) => { bind.push(value); return `$${bind.length}`; }; } format(value, field, options, bindParam) { options = options || {}; if (value !== null && value !== void 0) { if (value instanceof Utils.SequelizeMethod) { throw new Error("Cannot pass SequelizeMethod as a bind parameter - use escape instead"); } if (field && field.type) { this.validate(value, field, options); if (field.type.bindParam) { return field.type.bindParam(value, { escape: _.identity, field, timezone: this.options.timezone, operation: options.operation, bindParam }); } } } return bindParam(value); } validate(value, field, options) { if (this.typeValidation && field.type.validate && value) { try { if (options.isList && Array.isArray(value)) { for (const item of value) { field.type.validate(item, options); } } else { field.type.validate(value, options); } } catch (error) { if (error instanceof sequelizeError.ValidationError) { error.errors.push(new sequelizeError.ValidationErrorItem(error.message, "Validation error", field.fieldName, value, null, `${field.type.key} validator`)); } throw error; } } } isIdentifierQuoted(identifier) { return /^\s*(?:([`"'])(?:(?!\1).|\1{2})*\1\.?)+\s*$/i.test(identifier); } jsonPathExtractionQuery(column, path, isJson) { let paths = _.toPath(path); let pathStr; const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column); switch (this.dialect) { case "mysql": case "mariadb": case "sqlite": if (this.dialect === "mysql") { paths = paths.map((subPath) => { return /\D/.test(subPath) ? Utils.addTicks(subPath, '"') : subPath; }); } pathStr = this.escape(["$"].concat(paths).join(".").replace(/\.(\d+)(?:(?=\.)|$)/g, (__, digit) => `[${digit}]`)); if (this.dialect === "sqlite") { return `json_extract(${quotedColumn},${pathStr})`; } return `json_unquote(json_extract(${quotedColumn},${pathStr}))`; case "postgres": const join = isJson ? "#>" : "#>>"; pathStr = this.escape(`{${paths.join(",")}}`); return `(${quotedColumn}${join}${pathStr})`; default: throw new Error(`Unsupported ${this.dialect} for JSON operations`); } } selectQuery(tableName, options, model) { options = options || {}; const limit = options.limit; const mainQueryItems = []; const subQueryItems = []; const subQuery = options.subQuery === void 0 ? limit && options.hasMultiAssociation : options.subQuery; const attributes = { main: options.attributes && options.attributes.slice(), subQuery: null }; const mainTable = { name: tableName, quotedName: null, as: null, model }; const topLevelInfo = { names: mainTable, options, subQuery }; let mainJoinQueries = []; let subJoinQueries = []; let query; if (this.options.minifyAliases && !options.aliasesMapping) { options.aliasesMapping = /* @__PURE__ */ new Map(); options.aliasesByTable = {}; options.includeAliases = /* @__PURE__ */ new Map(); } if (options.tableAs) { mainTable.as = this.quoteIdentifier(options.tableAs); } else if (!Array.isArray(mainTable.name) && mainTable.model) { mainTable.as = this.quoteIdentifier(mainTable.model.name); } mainTable.quotedName = !Array.isArray(mainTable.name) ? this.quoteTable(mainTable.name) : tableName.map((t) => { return Array.isArray(t) ? this.quoteTable(t[0], t[1]) : this.quoteTable(t, true); }).join(", "); if (subQuery && attributes.main) { for (const keyAtt of mainTable.model.primaryKeyAttributes) { if (!attributes.main.some((attr) => keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1])) { attributes.main.push(mainTable.model.rawAttributes[keyAtt].field ? [keyAtt, mainTable.model.rawAttributes[keyAtt].field] : keyAtt); } } } attributes.main = this.escapeAttributes(attributes.main, options, mainTable.as); attributes.main = attributes.main || (options.include ? [`${mainTable.as}.*`] : ["*"]); if (subQuery || options.groupedLimit) { attributes.subQuery = attributes.main; attributes.main = [`${mainTable.as || mainTable.quotedName}.*`]; } if (options.include) { for (const include of options.include) { if (include.separate) { continue; } const joinQueries = this.generateInclude(include, { externalAs: mainTable.as, internalAs: mainTable.as }, topLevelInfo); subJoinQueries = subJoinQueries.concat(joinQueries.subQuery); mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); if (joinQueries.attributes.main.length > 0) { attributes.main = _.uniq(attributes.main.concat(joinQueries.attributes.main)); } if (joinQueries.attributes.subQuery.length > 0) { attributes.subQuery = _.uniq(attributes.subQuery.concat(joinQueries.attributes.subQuery)); } } } if (subQuery) { subQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.subQuery, mainTable.quotedName, mainTable.as)); subQueryItems.push(subJoinQueries.join("")); } else { if (options.groupedLimit) { if (!mainTable.as) { mainTable.as = mainTable.quotedName; } const where = __spreadValues({}, options.where); let groupedLimitOrder, whereKey, include, groupedTableName = mainTable.as; if (typeof options.groupedLimit.on === "string") { whereKey = options.groupedLimit.on; } else if (options.groupedLimit.on instanceof HasMany) { whereKey = options.groupedLimit.on.foreignKeyField; } if (options.groupedLimit.on instanceof BelongsToMany) { groupedTableName = options.groupedLimit.on.manyFromSource.as; const groupedLimitOptions = Model._validateIncludedElements({ include: [{ association: options.groupedLimit.on.manyFromSource, duplicating: false, required: true, where: __spreadValues({ [Op.placeholder]: true }, options.groupedLimit.through && options.groupedLimit.through.where) }], model }); options.hasJoin = true; options.hasMultiAssociation = true; options.includeMap = Object.assign(groupedLimitOptions.includeMap, options.includeMap); options.includeNames = groupedLimitOptions.includeNames.concat(options.includeNames || []); include = groupedLimitOptions.include; if (Array.isArray(options.order)) { options.order.forEach((order, i) => { if (Array.isArray(order)) { order = order[0]; } let alias = `subquery_order_${i}`; options.attributes.push([order, alias]); alias = this.sequelize.literal(this.quote(alias)); if (Array.isArray(options.order[i])) { options.order[i][0] = alias; } else { options.order[i] = alias; } }); groupedLimitOrder = options.order; } } else { groupedLimitOrder = options.order; if (!this._dialect.supports.topLevelOrderByRequired) { delete options.order; } where[Op.placeholder] = true; } const baseQuery = `SELECT * FROM (${this.selectQuery(tableName, { attributes: options.attributes, offset: options.offset, limit: options.groupedLimit.limit, order: groupedLimitOrder, aliasesMapping: options.aliasesMapping, aliasesByTable: options.aliasesByTable, where, include, model }, model).replace(/;$/, "")}) ${this.getAliasToken()} sub`; const placeHolder = this.whereItemQuery(Op.placeholder, true, { model }); const splicePos = baseQuery.indexOf(placeHolder); mainQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.main, `(${options.groupedLimit.values.map((value) => { let groupWhere; if (whereKey) { groupWhere = { [whereKey]: value }; } if (include) { groupWhere = { [options.groupedLimit.on.foreignIdentifierField]: value }; } return Utils.spliceStr(baseQuery, splicePos, placeHolder.length, this.getWhereConditions(groupWhere, groupedTableName)); }).join(this._dialect.supports["UNION ALL"] ? " UNION ALL " : " UNION ")})`, mainTable.as)); } else { mainQueryItems.push(this.selectFromTableFragment(options, mainTable.model, attributes.main, mainTable.quotedName, mainTable.as)); } mainQueryItems.push(mainJoinQueries.join("")); } if (Object.prototype.hasOwnProperty.call(options, "where") && !options.groupedLimit) { options.where = this.getWhereConditions(options.where, mainTable.as || tableName, model, options); if (options.where) { if (subQuery) { subQueryItems.push(` WHERE ${options.where}`); } else { mainQueryItems.push(` WHERE ${options.where}`); mainQueryItems.forEach((value, key) => { if (value.startsWith("SELECT")) { mainQueryItems[key] = this.selectFromTableFragment(options, model, attributes.main, mainTable.quotedName, mainTable.as, options.where); } }); } } } if (options.group) { options.group = Array.isArray(options.group) ? options.group.map((t) => this.aliasGrouping(t, model, mainTable.as, options)).join(", ") : this.aliasGrouping(options.group, model, mainTable.as, options); if (subQuery && options.group) { subQueryItems.push(` GROUP BY ${options.group}`); } else if (options.group) { mainQueryItems.push(` GROUP BY ${options.group}`); } } if (Object.prototype.hasOwnProperty.call(options, "having")) { options.having = this.getWhereConditions(options.having, tableName, model, options, false); if (options.having) { if (subQuery) { subQueryItems.push(` HAVING ${options.having}`); } else { mainQueryItems.push(` HAVING ${options.having}`); } } } if (options.order) { const orders = this.getQueryOrders(options, model, subQuery); if (orders.mainQueryOrder.length) { mainQueryItems.push(` ORDER BY ${orders.mainQueryOrder.join(", ")}`); } if (orders.subQueryOrder.length) { subQueryItems.push(` ORDER BY ${orders.subQueryOrder.join(", ")}`); } } const limitOrder = this.addLimitAndOffset(options, mainTable.model); if (limitOrder && !options.groupedLimit) { if (subQuery) { subQueryItems.push(limitOrder); } else { mainQueryItems.push(limitOrder); } } if (subQuery) { this._throwOnEmptyAttributes(attributes.main, { modelName: model && model.name, as: mainTable.as }); query = `SELECT ${attributes.main.join(", ")} FROM (${subQueryItems.join("")}) ${this.getAliasToken()} ${mainTable.as}${mainJoinQueries.join("")}${mainQueryItems.join("")}`; } else { query = mainQueryItems.join(""); } if (options.lock && this._dialect.supports.lock) { let lock = options.lock; if (typeof options.lock === "object") { lock = options.lock.level; } if (this._dialect.supports.lockKey && ["KEY SHARE", "NO KEY UPDATE"].includes(lock)) { query += ` FOR ${lock}`; } else if (lock === "SHARE") { query += ` ${this._dialect.supports.forShare}`; } else { query += " FOR UPDATE"; } if (this._dialect.supports.lockOf && options.lock.of && options.lock.of.prototype instanceof Model) { query += ` OF ${this.quoteTable(options.lock.of.name)}`; } if (this._dialect.supports.skipLocked && options.skipLocked) { query += " SKIP LOCKED"; } } return `${query};`; } aliasGrouping(field, model, tableName, options) { const src = Array.isArray(field) ? field[0] : field; return this.quote(this._getAliasForField(tableName, src, options) || src, model); } escapeAttributes(attributes, options, mainTableAs) { return attributes && attributes.map((attr) => { let addTable = true; if (attr instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(attr); } if (Array.isArray(attr)) { if (attr.length !== 2) { throw new Error(`${JSON.stringify(attr)} is not a valid attribute definition. Please use the following format: ['attribute definition', 'alias']`); } attr = attr.slice(); if (attr[0] instanceof Utils.SequelizeMethod) { attr[0] = this.handleSequelizeMethod(attr[0]); addTable = false; } else if (this.options.attributeBehavior === "escape" || !attr[0].includes("(") && !attr[0].includes(")")) { attr[0] = this.quoteIdentifier(attr[0]); } else if (this.options.attributeBehavior !== "unsafe-legacy") { throw new Error(`Attributes cannot include parentheses in Sequelize 6: In order to fix the vulnerability CVE-2023-22578, we had to remove support for treating attributes as raw SQL if they included parentheses. Sequelize 7 escapes all attributes, even if they include parentheses. For Sequelize 6, because we're introducing this change in a minor release, we've opted for throwing an error instead of silently escaping the attribute as a way to warn you about this change. Here is what you can do to fix this error: - Wrap the attribute in a literal() call. This will make Sequelize treat it as raw SQL. - Set the "attributeBehavior" sequelize option to "escape" to make Sequelize escape the attribute, like in Sequelize v7. We highly recommend this option. - Set the "attributeBehavior" sequelize option to "unsafe-legacy" to make Sequelize escape the attribute, like in Sequelize v5. We sincerely apologize for the inconvenience this may cause you. You can find more information on the following threads: https://github.com/sequelize/sequelize/security/advisories/GHSA-f598-mfpv-gmfx https://github.com/sequelize/sequelize/discussions/15694`); } let alias = attr[1]; if (this.options.minifyAliases) { alias = this._getMinifiedAlias(alias, mainTableAs, options); } attr = [attr[0], this.quoteIdentifier(alias)].join(" AS "); } else { attr = !attr.includes(Utils.TICK_CHAR) && !attr.includes('"') ? this.quoteAttribute(attr, options.model) : this.escape(attr); } if (!_.isEmpty(options.include) && (!attr.includes(".") || options.dotNotation) && addTable) { attr = `${mainTableAs}.${attr}`; } return attr; }); } generateInclude(include, parentTableName, topLevelInfo) { const joinQueries = { mainQuery: [], subQuery: [] }; const mainChildIncludes = []; const subChildIncludes = []; let requiredMismatch = false; const includeAs = { internalAs: include.as, externalAs: include.as }; const attributes = { main: [], subQuery: [] }; let joinQuery; topLevelInfo.options.keysEscaped = true; if (topLevelInfo.names.name !== parentTableName.externalAs && topLevelInfo.names.as !== parentTableName.externalAs) { includeAs.internalAs = `${parentTableName.internalAs}->${include.as}`; includeAs.externalAs = `${parentTableName.externalAs}.${include.as}`; } if (topLevelInfo.options.includeIgnoreAttributes !== false) { include.model._expandAttributes(include); Utils.mapFinderOptions(include, include.model); const includeAttributes = include.attributes.map((attr) => { let attrAs = attr; let verbatim = false; if (Array.isArray(attr) && attr.length === 2) { if (attr[0] instanceof Utils.SequelizeMethod && (attr[0] instanceof Utils.Literal || attr[0] instanceof Utils.Cast || attr[0] instanceof Utils.Fn)) { verbatim = true; } attr = attr.map((attr2) => attr2 instanceof Utils.SequelizeMethod ? this.handleSequelizeMethod(attr2) : attr2); attrAs = attr[1]; attr = attr[0]; } if (attr instanceof Utils.Literal) { return attr.val; } if (attr instanceof Utils.Cast || attr instanceof Utils.Fn) { throw new