12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744 |
- '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 || references.fields)) {
- throw new Error('references object with table and field must be specified');
- }
- constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`);
- const quotedReferences =
- typeof references.field !== 'undefined'
- ? this.quoteIdentifier(references.field)
- : references.fields.map(f => this.quoteIdentifier(f)).join(', ');
- const referencesSnippet = `${this.quoteTable(references.table)} (${quotedReferences})`;
- constraintSnippet = `CONSTRAINT ${constraintName} `;
- constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`;
- if (options.onUpdate) {
- constraintSnippet += ` ON UPDATE ${options.onUpdate.toUpperCase()}`;
- }
- if (options.onDelete) {
- constraintSnippet += ` ON DELETE ${options.onDelete.toUpperCase()}`;
- }
- break;
- default: throw new Error(`${options.type} is invalid.`);
- }
- if (options.deferrable && ['UNIQUE', 'PRIMARY KEY', 'FOREIGN KEY'].includes(options.type.toUpperCase())) {
- constraintSnippet += ` ${this.deferConstraintsQuery(options)}`;
- }
- return constraintSnippet;
- }
- removeConstraintQuery(tableName, constraintName) {
- if (typeof tableName === 'string') {
- tableName = this.quoteIdentifiers(tableName);
- } else {
- tableName = this.quoteTable(tableName);
- }
- return Utils.joinSQLFragments([
- 'ALTER TABLE',
- tableName,
- 'DROP CONSTRAINT',
- this.quoteIdentifiers(constraintName)
- ]);
- }
- /*
- Quote 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 ([this.OperatorMap[Op.between], this.OperatorMap[Op.notBetween]].includes(smth.comparator)) {
- value = `${this.escape(value[0])} AND ${this.escape(value[1])}`;
- } else 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;
|