123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733 |
- 'use strict';
- 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');
- const QuoteHelper = require('./query-generator/helpers/quote');
- /**
- * Abstract Query Generator
- *
- * @private
- */
- class QueryGenerator {
- constructor(options) {
- if (!options.sequelize) throw new Error('QueryGenerator initialized without options.sequelize');
- if (!options._dialect) throw new Error('QueryGenerator initialized without options._dialect');
- this.sequelize = options.sequelize;
- this.options = options.sequelize.options;
- // dialect name
- this.dialect = options._dialect.name;
- this._dialect = options._dialect;
- }
- extractTableDetails(tableName, options) {
- options = options || {};
- tableName = tableName || {};
- return {
- schema: tableName.schema || 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)};`;
- }
- /**
- * Returns an insert into command
- *
- * @param {string} table
- * @param {object} valueHash attribute value pairs
- * @param {object} modelAttributes
- * @param {object} [options]
- *
- * @private
- */
- insertQuery(table, valueHash, modelAttributes, options) {
- options = options || {};
- _.defaults(options, this.options);
- const modelAttributeMap = {};
- const bind = [];
- const fields = [];
- const returningModelAttributes = [];
- const values = [];
- const quotedTable = this.quoteTable(table);
- const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;
- let query;
- let valueQuery = '';
- let emptyQuery = '';
- let outputFragment = '';
- let returningFragment = '';
- let identityWrapperRequired = false;
- let tmpTable = ''; //tmpTable declaration for trigger
- 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 && options.returning) {
- const returnValues = this.generateReturnValues(modelAttributes, options);
- returningModelAttributes.push(...returnValues.returnFields);
- returningFragment = returnValues.returningFragment;
- tmpTable = returnValues.tmpTable || '';
- outputFragment = returnValues.outputFragment || '';
- }
- if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {
- // Not currently supported with search path (requires output of multiple queries)
- options.bindParam = false;
- }
- if (this._dialect.supports.EXCEPTION && options.exception) {
- // Not currently supported with bind parameters (requires output of multiple queries)
- 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));
- // SERIALS' can't be NULL in postgresql, use DEFAULT where supported
- if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && !value) {
- 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] || undefined, { context: 'INSERT' }));
- } else {
- values.push(this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }, bindParam));
- }
- }
- }
- }
- let onDuplicateKeyUpdate = '';
- if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) {
- if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite
- // If no conflict target columns were specified, use the primary key names from options.upsertKeys
- const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));
- const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`);
- onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`;
- } else {
- const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`);
- 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}`;
- // Mostly for internal use, so we expect the user to know what he's doing!
- // pg_temp functions are private per connection, so we never risk this function interfering with another one.
- 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;
- }
- query = `${replacements.attributes.length ? valueQuery : emptyQuery};`;
- if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) {
- query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`;
- }
- // Used by Postgres upsertQuery and calls to here with options.exception set to true
- const result = { query };
- if (options.bindParam !== false) {
- result.bind = bind;
- }
- return result;
- }
- /**
- * Returns an insert into command for multiple values.
- *
- * @param {string} tableName
- * @param {object} fieldValueHashes
- * @param {object} options
- * @param {object} fieldMappedAttributes
- *
- * @private
- */
- bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) {
- options = options || {};
- fieldMappedAttributes = fieldMappedAttributes || {};
- 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] || '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') { // postgres / sqlite
- // If no conflict target columns were specified, use the primary key names from options.upsertKeys
- const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr));
- const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`);
- onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`;
- } else { // mysql / maria
- 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,
- ';'
- ]);
- }
- /**
- * Returns an update query
- *
- * @param {string} tableName
- * @param {object} attrValueHash
- * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer
- * @param {object} options
- * @param {object} attributes
- *
- * @private
- */
- 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 = ''; // tmpTable declaration for trigger
- let suffix = '';
- if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {
- // Not currently supported with search path (requires output of multiple queries)
- options.bindParam = false;
- }
- const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;
- if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) {
- if (this.dialect !== 'mssql') {
- suffix = ` LIMIT ${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 || '';
- // ensure that the return output is properly mapped to model fields.
- 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) {
- // not allowed to update identity column
- continue;
- }
- const value = attrValueHash[key];
- if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {
- values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })}`);
- } else {
- values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }, bindParam)}`);
- }
- }
- const whereOptions = { ...options, bindParam };
- if (values.length === 0) {
- return '';
- }
- const query = `${tmpTable}UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where, whereOptions)}${suffix}`.trim();
- // Used by Postgres upsertQuery and calls to here with options.exception set to true
- const result = { query };
- if (options.bindParam !== false) {
- result.bind = bind;
- }
- return result;
- }
- /**
- * Returns an update query using arithmetic operator
- *
- * @param {string} operator String with the arithmetic operator (e.g. '+' or '-')
- * @param {string} tableName Name of the table
- * @param {object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer
- * @param {object} incrementAmountsByField A plain-object with attribute-value-pairs
- * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs
- * @param {object} options
- *
- * @private
- */
- 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
- ]);
- }
- /*
- Returns an add index query.
- Parameters:
- - tableName -> Name of an existing table, possibly with schema.
- - options:
- - type: UNIQUE|FULLTEXT|SPATIAL
- - name: The name of the index. Default is <table>_<attr1>_<attr2>
- - fields: An array of attributes as string or as hash.
- If the attribute is a hash, it must have the following content:
- - name: The name of the attribute/column
- - length: An integer. Optional
- - order: 'ASC' or 'DESC'. Optional
- - parser
- - using
- - operator
- - concurrently: Pass CONCURRENT so other operations run while the index is created
- - rawTablename, the name of the table, without schema. Used to create the name of the index
- @private
- */
- addIndexQuery(tableName, attributes, options, rawTablename) {
- options = options || {};
- if (!Array.isArray(attributes)) {
- options = attributes;
- attributes = undefined;
- } 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) {
- // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations)
- // All calls that go through sequelize should already have a 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' : undefined;
- 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 : undefined,
- this.quoteIdentifiers(options.name),
- this._dialect.supports.index.using === 1 && options.using ? `USING ${options.using}` : '',
- !this._dialect.supports.indexViaAlter ? `ON ${tableName}` : undefined,
- this._dialect.supports.index.using === 2 && options.using ? `USING ${options.using}` : '',
- `(${fieldsSql.join(', ')})`,
- this._dialect.supports.index.parser && options.parser ? `WITH PARSER ${options.parser}` : undefined,
- this._dialect.supports.index.where && options.where ? options.where : undefined
- );
- 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 === undefined) {
- throw new Error('Default value must be specifed 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) {
- throw new Error('references object with table and field must be specified');
- }
- constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`);
- const referencesSnippet = `${this.quoteTable(references.table)} (${this.quoteIdentifier(references.field)})`;
- 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.`);
- }
- 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 an object based on its type. This is a more general version of quoteIdentifiers
- Strings: should proxy to quoteIdentifiers
- Arrays:
- * Expects array in the form: [<model> (optional), <model> (optional),... String, String (optional)]
- Each <model> can be a model, or an object {model: Model, as: String}, matching include, or an
- association object, or the name of an association.
- * Zero or more models can be included in the array and are used to trace a path through the tree of
- included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL
- and quotes it.
- * If a single string is appended to end of array, it is quoted.
- If two strings appended, the 1st string is quoted, the 2nd string unquoted.
- Objects:
- * If raw is set, that value should be returned verbatim, without quoting
- * If fn is set, the string should start with the value of fn, starting paren, followed by
- the values of cols (which is assumed to be an array), quoted and joined with ', ',
- unless they are themselves objects
- * If direction is set, should be prepended
- Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could
- potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
- @private
- */
- quote(collection, parent, connector) {
- // init
- const validOrderOptions = [
- 'ASC',
- 'DESC',
- 'ASC NULLS LAST',
- 'DESC NULLS LAST',
- 'ASC NULLS FIRST',
- 'DESC NULLS FIRST',
- 'NULLS FIRST',
- 'NULLS LAST'
- ];
- // default
- connector = connector || '.';
- // just quote as identifiers if string
- if (typeof collection === 'string') {
- return this.quoteIdentifiers(collection);
- }
- if (Array.isArray(collection)) {
- // iterate through the collection and mutate objects into associations
- collection.forEach((item, index) => {
- const previous = collection[index - 1];
- let previousAssociation;
- let previousModel;
- // set the previous as the parent when previous is undefined or the target of the association
- if (!previous && parent !== undefined) {
- previousModel = parent;
- } else if (previous && previous instanceof Association) {
- previousAssociation = previous;
- previousModel = previous.target;
- }
- // if the previous item is a model, then attempt getting an association
- if (previousModel && previousModel.prototype instanceof Model) {
- let model;
- let as;
- if (typeof item === 'function' && item.prototype instanceof Model) {
- // set
- model = item;
- } else if (_.isPlainObject(item) && item.model && item.model.prototype instanceof Model) {
- // set
- model = item.model;
- as = item.as;
- }
- if (model) {
- // set the as to either the through name or the model name
- if (!as && previousAssociation && previousAssociation instanceof Association && previousAssociation.through && previousAssociation.through.model === model) {
- // get from previous association
- item = new Association(previousModel, model, {
- as: model.name
- });
- } else {
- // get association from previous model
- item = previousModel.getAssociationForAlias(model, as);
- // attempt to use the model name if the item is still null
- if (!item) {
- item = previousModel.getAssociationForAlias(model, model.name);
- }
- }
- // make sure we have an association
- if (!(item instanceof Association)) {
- throw new Error(util.format('Unable to find a valid association for model, \'%s\'', model.name));
- }
- }
- }
- if (typeof item === 'string') {
- // get order index
- const orderIndex = validOrderOptions.indexOf(item.toUpperCase());
- // see if this is an order
- if (index > 0 && orderIndex !== -1) {
- item = this.sequelize.literal(` ${validOrderOptions[orderIndex]}`);
- } else if (previousModel && previousModel.prototype instanceof Model) {
- // only go down this path if we have preivous model and check only once
- if (previousModel.associations !== undefined && previousModel.associations[item]) {
- // convert the item to an association
- item = previousModel.associations[item];
- } else if (previousModel.rawAttributes !== undefined && previousModel.rawAttributes[item] && item !== previousModel.rawAttributes[item].field) {
- // convert the item attribute from its alias
- item = previousModel.rawAttributes[item].field;
- } else if (
- item.includes('.')
- && previousModel.rawAttributes !== undefined
- ) {
- const itemSplit = item.split('.');
- if (previousModel.rawAttributes[itemSplit[0]].type instanceof DataTypes.JSON) {
- // just quote identifiers for now
- const identifier = this.quoteIdentifiers(`${previousModel.name}.${previousModel.rawAttributes[itemSplit[0]].field}`);
- // get path
- const path = itemSplit.slice(1);
- // extract path
- item = this.jsonPathExtractionQuery(identifier, path);
- // literal because we don't want to append the model name when string
- item = this.sequelize.literal(item);
- }
- }
- }
- }
- collection[index] = item;
- }, this);
- // loop through array, adding table names of models to quoted
- 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;
- }
- }
- // start building sql
- let sql = '';
- if (i > 0) {
- sql += `${this.quoteIdentifier(tableNames.join(connector))}.`;
- } else if (typeof collection[0] === 'string' && parent) {
- sql += `${this.quoteIdentifier(parent.name)}.`;
- }
- // loop through everything past i and append to the sql
- 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) {
- // simple objects with raw is no longer supported
- 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)}`);
- }
- /**
- * Split a list of identifiers by "." and quote each part
- *
- * @param {string} identifier
- * @param {boolean} force
- *
- * @returns {string}
- */
- quoteIdentifier(identifier, force) {
- return QuoteHelper.quoteIdentifier(this.dialect, identifier, {
- force,
- quoteIdentifiers: this.options.quoteIdentifiers
- });
- }
- 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);
- }
- /**
- * Quote table name with optional alias and schema attribution
- *
- * @param {string|object} param table string or object
- * @param {string|boolean} alias alias name
- *
- * @returns {string}
- */
- quoteTable(param, alias) {
- let table = '';
- if (alias === true) {
- alias = param.as || param.name || param;
- }
- if (_.isObject(param)) {
- if (this._dialect.supports.schemas) {
- if (param.schema) {
- table += `${this.quoteIdentifier(param.schema)}.`;
- }
- table += this.quoteIdentifier(param.tableName);
- } else {
- if (param.schema) {
- table += param.schema + (param.delimiter || '.');
- }
- table += param.tableName;
- table = this.quoteIdentifier(table);
- }
- } else {
- table = this.quoteIdentifier(param);
- }
- if (alias) {
- table += ` AS ${this.quoteIdentifier(alias)}`;
- }
- return table;
- }
- /*
- Escape a value (e.g. a string, number or date)
- @private
- */
- escape(value, field, options) {
- options = options || {};
- if (value !== null && value !== undefined) {
- if (value instanceof Utils.SequelizeMethod) {
- return this.handleSequelizeMethod(value);
- }
- if (field && field.type) {
- this.validate(value, field, options);
- if (field.type.stringify) {
- // Users shouldn't have to worry about these args - just give them a function that takes a single arg
- 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) {
- // The data-type already did the required escaping
- return value;
- }
- }
- }
- }
- return SqlString.escape(value, this.options.timezone, this.dialect);
- }
- bindParam(bind) {
- return value => {
- bind.push(value);
- return `$${bind.length}`;
- };
- }
- /*
- Returns a bind parameter representation of a value (e.g. a string, number or date)
- @private
- */
- format(value, field, options, bindParam) {
- options = options || {};
- if (value !== null && value !== undefined) {
- 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 a value against a field specification
- @private
- */
- 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 QuoteHelper.isIdentifierQuoted(identifier);
- }
- /**
- * Generates an SQL query that extract JSON property of given path.
- *
- * @param {string} column The JSON column
- * @param {string|Array<string>} [path] The path to extract (optional)
- * @returns {string} The generated sql query
- * @private
- */
- jsonPathExtractionQuery(column, path) {
- let paths = _.toPath(path);
- let pathStr;
- const quotedColumn = this.isIdentifierQuoted(column)
- ? column
- : this.quoteIdentifier(column);
- switch (this.dialect) {
- case 'mysql':
- case 'mariadb':
- case 'sqlite':
- /**
- * Non digit sub paths need to be quoted as ECMAScript identifiers
- * https://bugs.mysql.com/bug.php?id=81896
- */
- 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':
- pathStr = this.escape(`{${paths.join(',')}}`);
- return `(${quotedColumn}#>>${pathStr})`;
- default:
- throw new Error(`Unsupported ${this.dialect} for JSON operations`);
- }
- }
- /*
- Returns a query for selecting elements in the table <tableName>.
- Options:
- - attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
- - where -> A hash with conditions (e.g. {name: 'foo'})
- OR an ID as integer
- - order -> e.g. 'id DESC'
- - group
- - limit -> The maximum count you want to get.
- - offset -> An offset value to start from. Only useable with limit!
- @private
- */
- selectQuery(tableName, options, model) {
- options = options || {};
- const limit = options.limit;
- const mainQueryItems = [];
- const subQueryItems = [];
- const subQuery = options.subQuery === undefined ? 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;
- // Aliases can be passed through subqueries and we don't want to reset them
- if (this.options.minifyAliases && !options.aliasesMapping) {
- options.aliasesMapping = new Map();
- options.aliasesByTable = {};
- options.includeAliases = new Map();
- }
- // resolve table name options
- 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) {
- // Check if mainAttributes contain the primary key of the model either as a field or an aliased field
- 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, we add the mainAttributes to the subQuery and set the mainAttributes to select * from subquery
- if (subQuery || options.groupedLimit) {
- // We need primary keys
- 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 = { ...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) {
- // BTM includes needs to join the through table on to check ID
- groupedTableName = options.groupedLimit.on.manyFromSource.as;
- const groupedLimitOptions = Model._validateIncludedElements({
- include: [{
- association: options.groupedLimit.on.manyFromSource,
- duplicating: false, // The UNION'ed query may contain duplicates, but each sub-query cannot
- required: true,
- where: {
- [Op.placeholder]: true,
- ...options.groupedLimit.through && options.groupedLimit.through.where
- }
- }],
- model
- });
- // Make sure attributes from the join table are mapped back to models
- 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)) {
- // We need to make sure the order by attributes are available to the parent query
- options.order.forEach((order, i) => {
- if (Array.isArray(order)) {
- order = order[0];
- }
- let alias = `subquery_order_${i}`;
- options.attributes.push([order, alias]);
- // We don't want to prepend model name when we alias the attributes, so quote them here
- 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 {
- // Ordering is handled by the subqueries, so ordering the UNION'ed result is not needed
- groupedLimitOrder = options.order;
- delete options.order;
- where[Op.placeholder] = true;
- }
- // Caching the base query and splicing the where part into it is consistently > twice
- // as fast than generating from scratch each time for values.length >= 5
- 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(/;$/, '')}) AS sub`; // Every derived table must have its own alias
- 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(''));
- }
- // Add WHERE to sub or main query
- 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}`);
- // Walk the main query to update all selects
- mainQueryItems.forEach((value, key) => {
- if (value.startsWith('SELECT')) {
- mainQueryItems[key] = this.selectFromTableFragment(options, model, attributes.main, mainTable.quotedName, mainTable.as, options.where);
- }
- });
- }
- }
- }
- // Add GROUP BY to sub or main query
- 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}`);
- }
- }
- // Add HAVING to sub or main query
- 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}`);
- }
- }
- }
- // Add ORDER to sub or main query
- 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(', ')}`);
- }
- }
- // Add LIMIT, OFFSET to sub or main query
- 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('')}) AS ${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 && (lock === 'KEY SHARE' || lock === 'NO KEY UPDATE')) {
- 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 (!attr[0].includes('(') && !attr[0].includes(')')) {
- attr[0] = this.quoteIdentifier(attr[0]);
- } else {
- deprecations.noRawAttributes();
- }
- 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('.') && 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}`;
- }
- // includeIgnoreAttributes is used by aggregate functions
- 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(attr => attr instanceof Utils.SequelizeMethod ? this.handleSequelizeMethod(attr) : attr);
- attrAs = attr[1];
- attr = attr[0];
- }
- if (attr instanceof Utils.Literal) {
- return attr.val; // We trust the user to rename the field correctly
- }
- if (attr instanceof Utils.Cast || attr instanceof Utils.Fn) {
- throw new Error(
- 'Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. ' +
- 'This means the attribute will not be added to the returned instance'
- );
- }
- let prefix;
- if (verbatim === true) {
- prefix = attr;
- } else if (/#>>|->>/.test(attr)) {
- prefix = `(${this.quoteIdentifier(includeAs.internalAs)}.${attr.replace(/\(|\)/g, '')})`;
- } else if (/json_extract\(/.test(attr)) {
- prefix = attr.replace(/json_extract\(/i, `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.`);
- } else {
- prefix = `${this.quoteIdentifier(includeAs.internalAs)}.${this.quoteIdentifier(attr)}`;
- }
- let alias = `${includeAs.externalAs}.${attrAs}`;
- if (this.options.minifyAliases) {
- alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options);
- }
- return Utils.joinSQLFragments([
- prefix,
- 'AS',
- this.quoteIdentifier(alias, true)
- ]);
- });
- if (include.subQuery && topLevelInfo.subQuery) {
- for (const attr of includeAttributes) {
- attributes.subQuery.push(attr);
- }
- } else {
- for (const attr of includeAttributes) {
- attributes.main.push(attr);
- }
- }
- }
- //through
- if (include.through) {
- joinQuery = this.generateThroughJoin(include, includeAs, parentTableName.internalAs, topLevelInfo);
- } else {
- this._generateSubQueryFilter(include, includeAs, topLevelInfo);
- joinQuery = this.generateJoin(include, topLevelInfo);
- }
- // handle possible new attributes created in join
- if (joinQuery.attributes.main.length > 0) {
- attributes.main = attributes.main.concat(joinQuery.attributes.main);
- }
- if (joinQuery.attributes.subQuery.length > 0) {
- attributes.subQuery = attributes.subQuery.concat(joinQuery.attributes.subQuery);
- }
- if (include.include) {
- for (const childInclude of include.include) {
- if (childInclude.separate || childInclude._pseudo) {
- continue;
- }
- const childJoinQueries = this.generateInclude(childInclude, includeAs, topLevelInfo);
- if (include.required === false && childInclude.required === true) {
- requiredMismatch = true;
- }
- // if the child is a sub query we just give it to the
- if (childInclude.subQuery && topLevelInfo.subQuery) {
- subChildIncludes.push(childJoinQueries.subQuery);
- }
- if (childJoinQueries.mainQuery) {
- mainChildIncludes.push(childJoinQueries.mainQuery);
- }
- if (childJoinQueries.attributes.main.length > 0) {
- attributes.main = attributes.main.concat(childJoinQueries.attributes.main);
- }
- if (childJoinQueries.attributes.subQuery.length > 0) {
- attributes.subQuery = attributes.subQuery.concat(childJoinQueries.attributes.subQuery);
- }
- }
- }
- if (include.subQuery && topLevelInfo.subQuery) {
- if (requiredMismatch && subChildIncludes.length > 0) {
- joinQueries.subQuery.push(` ${joinQuery.join} ( ${joinQuery.body}${subChildIncludes.join('')} ) ON ${joinQuery.condition}`);
- } else {
- joinQueries.subQuery.push(` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`);
- if (subChildIncludes.length > 0) {
- joinQueries.subQuery.push(subChildIncludes.join(''));
- }
- }
- joinQueries.mainQuery.push(mainChildIncludes.join(''));
- } else {
- if (requiredMismatch && mainChildIncludes.length > 0) {
- joinQueries.mainQuery.push(` ${joinQuery.join} ( ${joinQuery.body}${mainChildIncludes.join('')} ) ON ${joinQuery.condition}`);
- } else {
- joinQueries.mainQuery.push(` ${joinQuery.join} ${joinQuery.body} ON ${joinQuery.condition}`);
- if (mainChildIncludes.length > 0) {
- joinQueries.mainQuery.push(mainChildIncludes.join(''));
- }
- }
- joinQueries.subQuery.push(subChildIncludes.join(''));
- }
- return {
- mainQuery: joinQueries.mainQuery.join(''),
- subQuery: joinQueries.subQuery.join(''),
- attributes
- };
- }
- _getMinifiedAlias(alias, tableName, options) {
- // We do not want to re-alias in case of a subquery
- if (options.aliasesByTable[`${tableName}${alias}`]) {
- return options.aliasesByTable[`${tableName}${alias}`];
- }
- // Do not alias custom suquery_orders
- if (alias.match(/subquery_order_[0-9]/)) {
- return alias;
- }
- const minifiedAlias = `_${options.aliasesMapping.size}`;
- options.aliasesMapping.set(minifiedAlias, alias);
- options.aliasesByTable[`${tableName}${alias}`] = minifiedAlias;
- return minifiedAlias;
- }
- _getAliasForField(tableName, field, options) {
- if (this.options.minifyAliases) {
- if (options.aliasesByTable[`${tableName}${field}`]) {
- return options.aliasesByTable[`${tableName}${field}`];
- }
- }
- return null;
- }
- generateJoin(include, topLevelInfo) {
- const association = include.association;
- const parent = include.parent;
- const parentIsTop = !!parent && !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name;
- let $parent;
- let joinWhere;
- /* Attributes for the left side */
- const left = association.source;
- const attrLeft = association instanceof BelongsTo ?
- association.identifier :
- association.sourceKeyAttribute || left.primaryKeyAttribute;
- const fieldLeft = association instanceof BelongsTo ?
- association.identifierField :
- left.rawAttributes[association.sourceKeyAttribute || left.primaryKeyAttribute].field;
- let asLeft;
- /* Attributes for the right side */
- const right = include.model;
- const tableRight = right.getTableName();
- const fieldRight = association instanceof BelongsTo ?
- right.rawAttributes[association.targetIdentifier || right.primaryKeyAttribute].field :
- association.identifierField;
- let asRight = include.as;
- while (($parent = $parent && $parent.parent || include.parent) && $parent.association) {
- if (asLeft) {
- asLeft = `${$parent.as}->${asLeft}`;
- } else {
- asLeft = $parent.as;
- }
- }
- if (!asLeft) asLeft = parent.as || parent.model.name;
- else asRight = `${asLeft}->${asRight}`;
- let joinOn = `${this.quoteTable(asLeft)}.${this.quoteIdentifier(fieldLeft)}`;
- const subqueryAttributes = [];
- if (topLevelInfo.options.groupedLimit && parentIsTop || topLevelInfo.subQuery && include.parent.subQuery && !include.subQuery) {
- if (parentIsTop) {
- // The main model attributes is not aliased to a prefix
- const tableName = this.quoteTable(parent.as || parent.model.name);
- // Check for potential aliased JOIN condition
- joinOn = this._getAliasForField(tableName, attrLeft, topLevelInfo.options) || `${tableName}.${this.quoteIdentifier(attrLeft)}`;
- if (topLevelInfo.subQuery) {
- subqueryAttributes.push(`${tableName}.${this.quoteIdentifier(fieldLeft)}`);
- }
- } else {
- const joinSource = `${asLeft.replace(/->/g, '.')}.${attrLeft}`;
- // Check for potential aliased JOIN condition
- joinOn = this._getAliasForField(asLeft, joinSource, topLevelInfo.options) || this.quoteIdentifier(joinSource);
- }
- }
- joinOn += ` = ${this.quoteIdentifier(asRight)}.${this.quoteIdentifier(fieldRight)}`;
- if (include.on) {
- joinOn = this.whereItemsQuery(include.on, {
- prefix: this.sequelize.literal(this.quoteIdentifier(asRight)),
- model: include.model
- });
- }
- if (include.where) {
- joinWhere = this.whereItemsQuery(include.where, {
- prefix: this.sequelize.literal(this.quoteIdentifier(asRight)),
- model: include.model
- });
- if (joinWhere) {
- if (include.or) {
- joinOn += ` OR ${joinWhere}`;
- } else {
- joinOn += ` AND ${joinWhere}`;
- }
- }
- }
- if (this.options.minifyAliases && asRight.length > 63) {
- const alias = `%${topLevelInfo.options.includeAliases.size}`;
- topLevelInfo.options.includeAliases.set(alias, asRight);
- }
- return {
- join: include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN',
- body: this.quoteTable(tableRight, asRight),
- condition: joinOn,
- attributes: {
- main: [],
- subQuery: subqueryAttributes
- }
- };
- }
- /**
- * Returns the SQL fragments to handle returning the attributes from an insert/update query.
- *
- * @param {object} modelAttributes An object with the model attributes.
- * @param {object} options An object with options.
- *
- * @private
- */
- generateReturnValues(modelAttributes, options) {
- const returnFields = [];
- const returnTypes = [];
- let outputFragment = '';
- let returningFragment = '';
- let tmpTable = '';
- if (Array.isArray(options.returning)) {
- returnFields.push(...options.returning.map(field => this.quoteIdentifier(field)));
- } else if (modelAttributes) {
- _.each(modelAttributes, attribute => {
- if (!(attribute.type instanceof DataTypes.VIRTUAL)) {
- returnFields.push(this.quoteIdentifier(attribute.field));
- returnTypes.push(attribute.type);
- }
- });
- }
- if (_.isEmpty(returnFields)) {
- returnFields.push('*');
- }
- if (this._dialect.supports.returnValues.returning) {
- returningFragment = ` RETURNING ${returnFields.join(',')}`;
- } else if (this._dialect.supports.returnValues.output) {
- outputFragment = ` OUTPUT ${returnFields.map(field => `INSERTED.${field}`).join(',')}`;
- //To capture output rows when there is a trigger on MSSQL DB
- if (options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
- const tmpColumns = returnFields.map((field, i) => `${field} ${returnTypes[i].toSql()}`);
- tmpTable = `DECLARE @tmp TABLE (${tmpColumns.join(',')}); `;
- outputFragment += ' INTO @tmp';
- returningFragment = '; SELECT * FROM @tmp';
- }
- }
- return { outputFragment, returnFields, returningFragment, tmpTable };
- }
- generateThroughJoin(include, includeAs, parentTableName, topLevelInfo) {
- const through = include.through;
- const throughTable = through.model.getTableName();
- const throughAs = `${includeAs.internalAs}->${through.as}`;
- const externalThroughAs = `${includeAs.externalAs}.${through.as}`;
- const throughAttributes = through.attributes.map(attr => {
- let alias = `${externalThroughAs}.${Array.isArray(attr) ? attr[1] : attr}`;
- if (this.options.minifyAliases) {
- alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options);
- }
- return Utils.joinSQLFragments([
- `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)}`,
- 'AS',
- this.quoteIdentifier(alias)
- ]);
- });
- const association = include.association;
- const parentIsTop = !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name;
- const tableSource = parentTableName;
- const identSource = association.identifierField;
- const tableTarget = includeAs.internalAs;
- const identTarget = association.foreignIdentifierField;
- const attrTarget = association.targetKeyField;
- const joinType = include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN';
- let joinBody;
- let joinCondition;
- const attributes = {
- main: [],
- subQuery: []
- };
- let attrSource = association.sourceKey;
- let sourceJoinOn;
- let targetJoinOn;
- let throughWhere;
- let targetWhere;
- if (topLevelInfo.options.includeIgnoreAttributes !== false) {
- // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
- for (const attr of throughAttributes) {
- attributes.main.push(attr);
- }
- }
- // Figure out if we need to use field or attribute
- if (!topLevelInfo.subQuery) {
- attrSource = association.sourceKeyField;
- }
- if (topLevelInfo.subQuery && !include.subQuery && !include.parent.subQuery && include.parent.model !== topLevelInfo.options.mainModel) {
- attrSource = association.sourceKeyField;
- }
- // Filter statement for left side of through
- // Used by both join and subquery where
- // If parent include was in a subquery need to join on the aliased attribute
- if (topLevelInfo.subQuery && !include.subQuery && include.parent.subQuery && !parentIsTop) {
- // If we are minifying aliases and our JOIN target has been minified, we need to use the alias instead of the original column name
- const joinSource = this._getAliasForField(tableSource, `${tableSource}.${attrSource}`, topLevelInfo.options) || `${tableSource}.${attrSource}`;
- sourceJoinOn = `${this.quoteIdentifier(joinSource)} = `;
- } else {
- // If we are minifying aliases and our JOIN target has been minified, we need to use the alias instead of the original column name
- const aliasedSource = this._getAliasForField(tableSource, attrSource, topLevelInfo.options) || attrSource;
- sourceJoinOn = `${this.quoteTable(tableSource)}.${this.quoteIdentifier(aliasedSource)} = `;
- }
- sourceJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identSource)}`;
- // Filter statement for right side of through
- // Used by both join and subquery where
- targetJoinOn = `${this.quoteIdentifier(tableTarget)}.${this.quoteIdentifier(attrTarget)} = `;
- targetJoinOn += `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(identTarget)}`;
- if (through.where) {
- throughWhere = this.getWhereConditions(through.where, this.sequelize.literal(this.quoteIdentifier(throughAs)), through.model);
- }
- if (this._dialect.supports.joinTableDependent) {
- // Generate a wrapped join so that the through table join can be dependent on the target join
- joinBody = `( ${this.quoteTable(throughTable, throughAs)} INNER JOIN ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)} ON ${targetJoinOn}`;
- if (throughWhere) {
- joinBody += ` AND ${throughWhere}`;
- }
- joinBody += ')';
- joinCondition = sourceJoinOn;
- } else {
- // Generate join SQL for left side of through
- joinBody = `${this.quoteTable(throughTable, throughAs)} ON ${sourceJoinOn} ${joinType} ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)}`;
- joinCondition = targetJoinOn;
- if (throughWhere) {
- joinCondition += ` AND ${throughWhere}`;
- }
- }
- if (include.where || include.through.where) {
- if (include.where) {
- targetWhere = this.getWhereConditions(include.where, this.sequelize.literal(this.quoteIdentifier(includeAs.internalAs)), include.model, topLevelInfo.options);
- if (targetWhere) {
- joinCondition += ` AND ${targetWhere}`;
- }
- }
- }
- this._generateSubQueryFilter(include, includeAs, topLevelInfo);
- return {
- join: joinType,
- body: joinBody,
- condition: joinCondition,
- attributes
- };
- }
- /*
- * Generates subQueryFilter - a select nested in the where clause of the subQuery.
- * For a given include a query is generated that contains all the way from the subQuery
- * table to the include table plus everything that's in required transitive closure of the
- * given include.
- */
- _generateSubQueryFilter(include, includeAs, topLevelInfo) {
- if (!topLevelInfo.subQuery || !include.subQueryFilter) {
- return;
- }
- if (!topLevelInfo.options.where) {
- topLevelInfo.options.where = {};
- }
- let parent = include;
- let child = include;
- let nestedIncludes = this._getRequiredClosure(include).include;
- let query;
- while ((parent = parent.parent)) { // eslint-disable-line
- if (parent.parent && !parent.required) {
- return; // only generate subQueryFilter if all the parents of this include are required
- }
- if (parent.subQueryFilter) {
- // the include is already handled as this parent has the include on its required closure
- // skip to prevent duplicate subQueryFilter
- return;
- }
- nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }];
- child = parent;
- }
- const topInclude = nestedIncludes[0];
- const topParent = topInclude.parent;
- const topAssociation = topInclude.association;
- topInclude.association = undefined;
- if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
- query = this.selectQuery(topInclude.through.model.getTableName(), {
- attributes: [topInclude.through.model.primaryKeyField],
- include: Model._validateIncludedElements({
- model: topInclude.through.model,
- include: [{
- association: topAssociation.toTarget,
- required: true,
- where: topInclude.where,
- include: topInclude.include
- }]
- }).include,
- model: topInclude.through.model,
- where: {
- [Op.and]: [
- this.sequelize.literal([
- `${this.quoteTable(topParent.model.name)}.${this.quoteIdentifier(topParent.model.primaryKeyField)}`,
- `${this.quoteIdentifier(topInclude.through.model.name)}.${this.quoteIdentifier(topAssociation.identifierField)}`
- ].join(' = ')),
- topInclude.through.where
- ]
- },
- limit: 1,
- includeIgnoreAttributes: false
- }, topInclude.through.model);
- } else {
- const isBelongsTo = topAssociation.associationType === 'BelongsTo';
- const sourceField = isBelongsTo ? topAssociation.identifierField : topAssociation.sourceKeyField || topParent.model.primaryKeyField;
- const targetField = isBelongsTo ? topAssociation.sourceKeyField || topInclude.model.primaryKeyField : topAssociation.identifierField;
- const join = [
- `${this.quoteIdentifier(topInclude.as)}.${this.quoteIdentifier(targetField)}`,
- `${this.quoteTable(topParent.as || topParent.model.name)}.${this.quoteIdentifier(sourceField)}`
- ].join(' = ');
- query = this.selectQuery(topInclude.model.getTableName(), {
- attributes: [targetField],
- include: Model._validateIncludedElements(topInclude).include,
- model: topInclude.model,
- where: {
- [Op.and]: [
- topInclude.where,
- { [Op.join]: this.sequelize.literal(join) }
- ]
- },
- limit: 1,
- tableAs: topInclude.as,
- includeIgnoreAttributes: false
- }, topInclude.model);
- }
- if (!topLevelInfo.options.where[Op.and]) {
- topLevelInfo.options.where[Op.and] = [];
- }
- topLevelInfo.options.where[`__${includeAs.internalAs}`] = this.sequelize.literal([
- '(',
- query.replace(/;$/, ''),
- ')',
- 'IS NOT NULL'
- ].join(' '));
- }
- /*
- * For a given include hierarchy creates a copy of it where only the required includes
- * are preserved.
- */
- _getRequiredClosure(include) {
- const copy = { ...include, attributes: [], include: [] };
- if (Array.isArray(include.include)) {
- copy.include = include.include
- .filter(i => i.required)
- .map(inc => this._getRequiredClosure(inc));
- }
- return copy;
- }
- getQueryOrders(options, model, subQuery) {
- const mainQueryOrder = [];
- const subQueryOrder = [];
- if (Array.isArray(options.order)) {
- for (let order of options.order) {
- // wrap if not array
- if (!Array.isArray(order)) {
- order = [order];
- }
- if (
- subQuery
- && Array.isArray(order)
- && order[0]
- && !(order[0] instanceof Association)
- && !(typeof order[0] === 'function' && order[0].prototype instanceof Model)
- && !(typeof order[0].model === 'function' && order[0].model.prototype instanceof Model)
- && !(typeof order[0] === 'string' && model && model.associations !== undefined && model.associations[order[0]])
- ) {
- subQueryOrder.push(this.quote(order, model, '->'));
- }
- if (subQuery) {
- // Handle case where sub-query renames attribute we want to order by,
- // see https://github.com/sequelize/sequelize/issues/8739
- const subQueryAttribute = options.attributes.find(a => Array.isArray(a) && a[0] === order[0] && a[1]);
- if (subQueryAttribute) {
- const modelName = this.quoteIdentifier(model.name);
- order[0] = new Utils.Col(this._getAliasForField(modelName, subQueryAttribute[1], options) || subQueryAttribute[1]);
- }
- }
- mainQueryOrder.push(this.quote(order, model, '->'));
- }
- } else if (options.order instanceof Utils.SequelizeMethod) {
- const sql = this.quote(options.order, model, '->');
- if (subQuery) {
- subQueryOrder.push(sql);
- }
- mainQueryOrder.push(sql);
- } else {
- throw new Error('Order must be type of array or instance of a valid sequelize method.');
- }
- return { mainQueryOrder, subQueryOrder };
- }
- _throwOnEmptyAttributes(attributes, extraInfo = {}) {
- if (attributes.length > 0) return;
- const asPart = extraInfo.as && `as ${extraInfo.as}` || '';
- const namePart = extraInfo.modelName && `for model '${extraInfo.modelName}'` || '';
- const message = `Attempted a SELECT query ${namePart} ${asPart} without selecting any columns`;
- throw new sequelizeError.QueryError(message.replace(/ +/g, ' '));
- }
- selectFromTableFragment(options, model, attributes, tables, mainTableAs) {
- this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs });
- let fragment = `SELECT ${attributes.join(', ')} FROM ${tables}`;
- if (mainTableAs) {
- fragment += ` AS ${mainTableAs}`;
- }
- if (options.indexHints && this._dialect.supports.indexHints) {
- for (const hint of options.indexHints) {
- if (IndexHints[hint.type]) {
- fragment += ` ${IndexHints[hint.type]} INDEX (${hint.values.map(indexName => this.quoteIdentifiers(indexName)).join(',')})`;
- }
- }
- }
- return fragment;
- }
- /**
- * Returns an SQL fragment for adding result constraints.
- *
- * @param {object} options An object with selectQuery options.
- * @returns {string} The generated sql query.
- * @private
- */
- addLimitAndOffset(options) {
- let fragment = '';
- /* eslint-disable */
- if (options.offset != null && options.limit == null) {
- fragment += ' LIMIT ' + this.escape(options.offset) + ', ' + 10000000000000;
- } else if (options.limit != null) {
- if (options.offset != null) {
- fragment += ' LIMIT ' + this.escape(options.offset) + ', ' + this.escape(options.limit);
- } else {
- fragment += ' LIMIT ' + this.escape(options.limit);
- }
- }
- /* eslint-enable */
- return fragment;
- }
- handleSequelizeMethod(smth, tableName, factory, options, prepend) {
- let result;
- if (Object.prototype.hasOwnProperty.call(this.OperatorMap, smth.comparator)) {
- smth.comparator = this.OperatorMap[smth.comparator];
- }
- if (smth instanceof Utils.Where) {
- let value = smth.logic;
- let key;
- if (smth.attribute instanceof Utils.SequelizeMethod) {
- key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend);
- } else {
- key = `${this.quoteTable(smth.attribute.Model.name)}.${this.quoteIdentifier(smth.attribute.field || smth.attribute.fieldName)}`;
- }
- if (value && value instanceof Utils.SequelizeMethod) {
- value = this.getWhereConditions(value, tableName, factory, options, prepend);
- if (value === 'NULL') {
- if (smth.comparator === '=') {
- smth.comparator = 'IS';
- }
- if (smth.comparator === '!=') {
- smth.comparator = 'IS NOT';
- }
- }
- return [key, value].join(` ${smth.comparator} `);
- }
- if (_.isPlainObject(value)) {
- return this.whereItemQuery(smth.attribute, value, {
- model: factory
- });
- }
- if (typeof value === 'boolean') {
- value = this.booleanValue(value);
- } else {
- value = this.escape(value);
- }
- if (value === 'NULL') {
- if (smth.comparator === '=') {
- smth.comparator = 'IS';
- }
- if (smth.comparator === '!=') {
- smth.comparator = 'IS NOT';
- }
- }
- return [key, value].join(` ${smth.comparator} `);
- }
- if (smth instanceof Utils.Literal) {
- return smth.val;
- }
- if (smth instanceof Utils.Cast) {
- if (smth.val instanceof Utils.SequelizeMethod) {
- result = this.handleSequelizeMethod(smth.val, tableName, factory, options, prepend);
- } else if (_.isPlainObject(smth.val)) {
- result = this.whereItemsQuery(smth.val);
- } else {
- result = this.escape(smth.val);
- }
- return `CAST(${result} AS ${smth.type.toUpperCase()})`;
- }
- if (smth instanceof Utils.Fn) {
- return `${smth.fn}(${
- smth.args.map(arg => {
- if (arg instanceof Utils.SequelizeMethod) {
- return this.handleSequelizeMethod(arg, tableName, factory, options, prepend);
- }
- if (_.isPlainObject(arg)) {
- return this.whereItemsQuery(arg);
- }
- return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg);
- }).join(', ')
- })`;
- }
- if (smth instanceof Utils.Col) {
- if (Array.isArray(smth.col) && !factory) {
- throw new Error('Cannot call Sequelize.col() with array outside of order / group clause');
- }
- if (smth.col.startsWith('*')) {
- return '*';
- }
- return this.quote(smth.col, factory);
- }
- return smth.toString(this, factory);
- }
- whereQuery(where, options) {
- const query = this.whereItemsQuery(where, options);
- if (query && query.length) {
- return `WHERE ${query}`;
- }
- return '';
- }
- whereItemsQuery(where, options, binding) {
- if (
- where === null ||
- where === undefined ||
- Utils.getComplexSize(where) === 0
- ) {
- // NO OP
- return '';
- }
- if (typeof where === 'string') {
- throw new Error('Support for `{where: \'raw query\'}` has been removed.');
- }
- const items = [];
- binding = binding || 'AND';
- if (binding[0] !== ' ') binding = ` ${binding} `;
- if (_.isPlainObject(where)) {
- Utils.getComplexKeys(where).forEach(prop => {
- const item = where[prop];
- items.push(this.whereItemQuery(prop, item, options));
- });
- } else {
- items.push(this.whereItemQuery(undefined, where, options));
- }
- return items.length && items.filter(item => item && item.length).join(binding) || '';
- }
- whereItemQuery(key, value, options = {}) {
- if (value === undefined) {
- throw new Error(`WHERE parameter "${key}" has invalid "undefined" value`);
- }
- if (typeof key === 'string' && key.includes('.') && options.model) {
- const keyParts = key.split('.');
- if (options.model.rawAttributes[keyParts[0]] && options.model.rawAttributes[keyParts[0]].type instanceof DataTypes.JSON) {
- const tmp = {};
- const field = options.model.rawAttributes[keyParts[0]];
- _.set(tmp, keyParts.slice(1), value);
- return this.whereItemQuery(field.field || keyParts[0], tmp, { field, ...options });
- }
- }
- const field = this._findField(key, options);
- const fieldType = field && field.type || options.type;
- const isPlainObject = _.isPlainObject(value);
- const isArray = !isPlainObject && Array.isArray(value);
- key = this.OperatorsAliasMap && this.OperatorsAliasMap[key] || key;
- if (isPlainObject) {
- value = this._replaceAliases(value);
- }
- const valueKeys = isPlainObject && Utils.getComplexKeys(value);
- if (key === undefined) {
- if (typeof value === 'string') {
- return value;
- }
- if (isPlainObject && valueKeys.length === 1) {
- return this.whereItemQuery(valueKeys[0], value[valueKeys[0]], options);
- }
- }
- if (value === null) {
- const opValue = options.bindParam ? 'NULL' : this.escape(value, field);
- return this._joinKeyValue(key, opValue, this.OperatorMap[Op.is], options.prefix);
- }
- if (!value) {
- const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field);
- return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix);
- }
- if (value instanceof Utils.SequelizeMethod && !(key !== undefined && value instanceof Utils.Fn)) {
- return this.handleSequelizeMethod(value);
- }
- // Convert where: [] to Op.and if possible, else treat as literal/replacements
- if (key === undefined && isArray) {
- if (Utils.canTreatArrayAsAnd(value)) {
- key = Op.and;
- } else {
- throw new Error('Support for literal replacements in the `where` object has been removed.');
- }
- }
- if (key === Op.or || key === Op.and || key === Op.not) {
- return this._whereGroupBind(key, value, options);
- }
- if (value[Op.or]) {
- return this._whereBind(this.OperatorMap[Op.or], key, value[Op.or], options);
- }
- if (value[Op.and]) {
- return this._whereBind(this.OperatorMap[Op.and], key, value[Op.and], options);
- }
- if (isArray && fieldType instanceof DataTypes.ARRAY) {
- const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field);
- return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix);
- }
- if (isPlainObject && fieldType instanceof DataTypes.JSON && options.json !== false) {
- return this._whereJSON(key, value, options);
- }
- // If multiple keys we combine the different logic conditions
- if (isPlainObject && valueKeys.length > 1) {
- return this._whereBind(this.OperatorMap[Op.and], key, value, options);
- }
- if (isArray) {
- return this._whereParseSingleValueObject(key, field, Op.in, value, options);
- }
- if (isPlainObject) {
- if (this.OperatorMap[valueKeys[0]]) {
- return this._whereParseSingleValueObject(key, field, valueKeys[0], value[valueKeys[0]], options);
- }
- return this._whereParseSingleValueObject(key, field, this.OperatorMap[Op.eq], value, options);
- }
- if (key === Op.placeholder) {
- const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field);
- return this._joinKeyValue(this.OperatorMap[key], opValue, this.OperatorMap[Op.eq], options.prefix);
- }
- const opValue = options.bindParam ? this.format(value, field, options, options.bindParam) : this.escape(value, field);
- return this._joinKeyValue(key, opValue, this.OperatorMap[Op.eq], options.prefix);
- }
- _findField(key, options) {
- if (options.field) {
- return options.field;
- }
- if (options.model && options.model.rawAttributes && options.model.rawAttributes[key]) {
- return options.model.rawAttributes[key];
- }
- if (options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]) {
- return options.model.fieldRawAttributesMap[key];
- }
- }
- // OR/AND/NOT grouping logic
- _whereGroupBind(key, value, options) {
- const binding = key === Op.or ? this.OperatorMap[Op.or] : this.OperatorMap[Op.and];
- const outerBinding = key === Op.not ? 'NOT ' : '';
- if (Array.isArray(value)) {
- value = value.map(item => {
- let itemQuery = this.whereItemsQuery(item, options, this.OperatorMap[Op.and]);
- if (itemQuery && itemQuery.length && (Array.isArray(item) || _.isPlainObject(item)) && Utils.getComplexSize(item) > 1) {
- itemQuery = `(${itemQuery})`;
- }
- return itemQuery;
- }).filter(item => item && item.length);
- value = value.length && value.join(binding);
- } else {
- value = this.whereItemsQuery(value, options, binding);
- }
- // Op.or: [] should return no data.
- // Op.not of no restriction should also return no data
- if ((key === Op.or || key === Op.not) && !value) {
- return '0 = 1';
- }
- return value ? `${outerBinding}(${value})` : undefined;
- }
- _whereBind(binding, key, value, options) {
- if (_.isPlainObject(value)) {
- value = Utils.getComplexKeys(value).map(prop => {
- const item = value[prop];
- return this.whereItemQuery(key, { [prop]: item }, options);
- });
- } else {
- value = value.map(item => this.whereItemQuery(key, item, options));
- }
- value = value.filter(item => item && item.length);
- return value.length ? `(${value.join(binding)})` : undefined;
- }
- _whereJSON(key, value, options) {
- const items = [];
- let baseKey = this.quoteIdentifier(key);
- if (options.prefix) {
- if (options.prefix instanceof Utils.Literal) {
- baseKey = `${this.handleSequelizeMethod(options.prefix)}.${baseKey}`;
- } else {
- baseKey = `${this.quoteTable(options.prefix)}.${baseKey}`;
- }
- }
- Utils.getOperators(value).forEach(op => {
- const where = {
- [op]: value[op]
- };
- items.push(this.whereItemQuery(key, where, { ...options, json: false }));
- });
- _.forOwn(value, (item, prop) => {
- this._traverseJSON(items, baseKey, prop, item, [prop]);
- });
- const result = items.join(this.OperatorMap[Op.and]);
- return items.length > 1 ? `(${result})` : result;
- }
- _traverseJSON(items, baseKey, prop, item, path) {
- let cast;
- if (path[path.length - 1].includes('::')) {
- const tmp = path[path.length - 1].split('::');
- cast = tmp[1];
- path[path.length - 1] = tmp[0];
- }
- const pathKey = this.jsonPathExtractionQuery(baseKey, path);
- if (_.isPlainObject(item)) {
- Utils.getOperators(item).forEach(op => {
- const value = this._toJSONValue(item[op]);
- items.push(this.whereItemQuery(this._castKey(pathKey, value, cast), { [op]: value }));
- });
- _.forOwn(item, (value, itemProp) => {
- this._traverseJSON(items, baseKey, itemProp, value, path.concat([itemProp]));
- });
- return;
- }
- item = this._toJSONValue(item);
- items.push(this.whereItemQuery(this._castKey(pathKey, item, cast), { [Op.eq]: item }));
- }
- _toJSONValue(value) {
- return value;
- }
- _castKey(key, value, cast, json) {
- cast = cast || this._getJsonCast(Array.isArray(value) ? value[0] : value);
- if (cast) {
- return new Utils.Literal(this.handleSequelizeMethod(new Utils.Cast(new Utils.Literal(key), cast, json)));
- }
- return new Utils.Literal(key);
- }
- _getJsonCast(value) {
- if (typeof value === 'number') {
- return 'double precision';
- }
- if (value instanceof Date) {
- return 'timestamptz';
- }
- if (typeof value === 'boolean') {
- return 'boolean';
- }
- return;
- }
- _joinKeyValue(key, value, comparator, prefix) {
- if (!key) {
- return value;
- }
- if (comparator === undefined) {
- throw new Error(`${key} and ${value} has no comparator`);
- }
- key = this._getSafeKey(key, prefix);
- return [key, value].join(` ${comparator} `);
- }
- _getSafeKey(key, prefix) {
- if (key instanceof Utils.SequelizeMethod) {
- key = this.handleSequelizeMethod(key);
- return this._prefixKey(this.handleSequelizeMethod(key), prefix);
- }
- if (Utils.isColString(key)) {
- key = key.substr(1, key.length - 2).split('.');
- if (key.length > 2) {
- key = [
- // join the tables by -> to match out internal namings
- key.slice(0, -1).join('->'),
- key[key.length - 1]
- ];
- }
- return key.map(identifier => this.quoteIdentifier(identifier)).join('.');
- }
- return this._prefixKey(this.quoteIdentifier(key), prefix);
- }
- _prefixKey(key, prefix) {
- if (prefix) {
- if (prefix instanceof Utils.Literal) {
- return [this.handleSequelizeMethod(prefix), key].join('.');
- }
- return [this.quoteTable(prefix), key].join('.');
- }
- return key;
- }
- _whereParseSingleValueObject(key, field, prop, value, options) {
- if (prop === Op.not) {
- if (Array.isArray(value)) {
- prop = Op.notIn;
- } else if (value !== null && value !== true && value !== false) {
- prop = Op.ne;
- }
- }
- let comparator = this.OperatorMap[prop] || this.OperatorMap[Op.eq];
- switch (prop) {
- case Op.in:
- case Op.notIn:
- if (value instanceof Utils.Literal) {
- return this._joinKeyValue(key, value.val, comparator, options.prefix);
- }
- if (value.length) {
- return this._joinKeyValue(key, `(${value.map(item => this.escape(item, field)).join(', ')})`, comparator, options.prefix);
- }
- if (comparator === this.OperatorMap[Op.in]) {
- return this._joinKeyValue(key, '(NULL)', comparator, options.prefix);
- }
- return '';
- case Op.any:
- case Op.all:
- comparator = `${this.OperatorMap[Op.eq]} ${comparator}`;
- if (value[Op.values]) {
- return this._joinKeyValue(key, `(VALUES ${value[Op.values].map(item => `(${this.escape(item)})`).join(', ')})`, comparator, options.prefix);
- }
- return this._joinKeyValue(key, `(${this.escape(value, field)})`, comparator, options.prefix);
- case Op.between:
- case Op.notBetween:
- return this._joinKeyValue(key, `${this.escape(value[0], field)} AND ${this.escape(value[1], field)}`, comparator, options.prefix);
- case Op.raw:
- throw new Error('The `$raw` where property is no longer supported. Use `sequelize.literal` instead.');
- case Op.col:
- comparator = this.OperatorMap[Op.eq];
- value = value.split('.');
- if (value.length > 2) {
- value = [
- // join the tables by -> to match out internal namings
- value.slice(0, -1).join('->'),
- value[value.length - 1]
- ];
- }
- return this._joinKeyValue(key, value.map(identifier => this.quoteIdentifier(identifier)).join('.'), comparator, options.prefix);
- case Op.startsWith:
- case Op.endsWith:
- case Op.substring:
- comparator = this.OperatorMap[Op.like];
- if (value instanceof Utils.Literal) {
- value = value.val;
- }
- let pattern = `${value}%`;
- if (prop === Op.endsWith) pattern = `%${value}`;
- if (prop === Op.substring) pattern = `%${value}%`;
- return this._joinKeyValue(key, this.escape(pattern), comparator, options.prefix);
- }
- const escapeOptions = {
- acceptStrings: comparator.includes(this.OperatorMap[Op.like])
- };
- if (_.isPlainObject(value)) {
- if (value[Op.col]) {
- return this._joinKeyValue(key, this.whereItemQuery(null, value), comparator, options.prefix);
- }
- if (value[Op.any]) {
- escapeOptions.isList = true;
- return this._joinKeyValue(key, `(${this.escape(value[Op.any], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.any]}`, options.prefix);
- }
- if (value[Op.all]) {
- escapeOptions.isList = true;
- return this._joinKeyValue(key, `(${this.escape(value[Op.all], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.all]}`, options.prefix);
- }
- }
- if (value === null && comparator === this.OperatorMap[Op.eq]) {
- return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.is], options.prefix);
- }
- if (value === null && comparator === this.OperatorMap[Op.ne]) {
- return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.not], options.prefix);
- }
- return this._joinKeyValue(key, this.escape(value, field, escapeOptions), comparator, options.prefix);
- }
- /*
- Takes something and transforms it into values of a where condition.
- @private
- */
- getWhereConditions(smth, tableName, factory, options, prepend) {
- const where = {};
- if (Array.isArray(tableName)) {
- tableName = tableName[0];
- if (Array.isArray(tableName)) {
- tableName = tableName[1];
- }
- }
- options = options || {};
- if (prepend === undefined) {
- prepend = true;
- }
- if (smth && smth instanceof Utils.SequelizeMethod) { // Checking a property is cheaper than a lot of instanceof calls
- return this.handleSequelizeMethod(smth, tableName, factory, options, prepend);
- }
- if (_.isPlainObject(smth)) {
- return this.whereItemsQuery(smth, {
- model: factory,
- prefix: prepend && tableName,
- type: options.type
- });
- }
- if (typeof smth === 'number') {
- let primaryKeys = factory ? Object.keys(factory.primaryKeys) : [];
- if (primaryKeys.length > 0) {
- // Since we're just a number, assume only the first key
- primaryKeys = primaryKeys[0];
- } else {
- primaryKeys = 'id';
- }
- where[primaryKeys] = smth;
- return this.whereItemsQuery(where, {
- model: factory,
- prefix: prepend && tableName
- });
- }
- if (typeof smth === 'string') {
- return this.whereItemsQuery(smth, {
- model: factory,
- prefix: prepend && tableName
- });
- }
- if (Buffer.isBuffer(smth)) {
- return this.escape(smth);
- }
- if (Array.isArray(smth)) {
- if (smth.length === 0 || smth.length > 0 && smth[0].length === 0) return '1=1';
- if (Utils.canTreatArrayAsAnd(smth)) {
- const _smth = { [Op.and]: smth };
- return this.getWhereConditions(_smth, tableName, factory, options, prepend);
- }
- throw new Error('Support for literal replacements in the `where` object has been removed.');
- }
- if (smth === null) {
- return this.whereItemsQuery(smth, {
- model: factory,
- prefix: prepend && tableName
- });
- }
- return '1=1';
- }
- // A recursive parser for nested where conditions
- parseConditionObject(conditions, path) {
- path = path || [];
- return _.reduce(conditions, (result, value, key) => {
- if (_.isObject(value)) {
- return result.concat(this.parseConditionObject(value, path.concat(key))); // Recursively parse objects
- }
- result.push({ path: path.concat(key), value });
- return result;
- }, []);
- }
- booleanValue(value) {
- return value;
- }
- }
- Object.assign(QueryGenerator.prototype, require('./query-generator/operators'));
- Object.assign(QueryGenerator.prototype, require('./query-generator/transaction'));
- module.exports = QueryGenerator;
|