where.test.ts 116 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010
  1. import type {
  2. AttributeNames,
  3. Attributes,
  4. Cast,
  5. Col,
  6. Fn,
  7. InferAttributes,
  8. Literal,
  9. Range,
  10. WhereOperators,
  11. WhereOptions,
  12. } from '@sequelize/core';
  13. import { DataTypes, JSON_NULL, Model, Op, SQL_NULL, and, json, or, sql } from '@sequelize/core';
  14. import type { FormatWhereOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/abstract-dialect/query-generator-typescript.js';
  15. import { expect } from 'chai';
  16. import { expectTypeOf } from 'expect-type';
  17. import attempt from 'lodash/attempt';
  18. import util from 'node:util';
  19. import { createTester, expectsql, getTestDialectTeaser, sequelize } from '../../support';
  20. const { literal, col, where, fn, cast, attribute } = sql;
  21. const queryGen = sequelize.dialect.queryGenerator;
  22. // Notice: [] will be replaced by dialect specific tick/quote character
  23. // when there is no dialect specific expectation but only a default expectation
  24. // TODO: fix and resolve any .skip test
  25. type Expectations = {
  26. [dialectName: string]: string | Error;
  27. };
  28. const dialectSupportsBigInt = () => sequelize.dialect.supports.dataTypes.BIGINT;
  29. const dialectSupportsArray = () => sequelize.dialect.supports.dataTypes.ARRAY;
  30. const dialectSupportsRange = () => sequelize.dialect.supports.dataTypes.RANGE;
  31. const dialectSupportsJsonB = () => sequelize.dialect.supports.dataTypes.JSONB;
  32. const dialectSupportsJson = () => sequelize.dialect.supports.dataTypes.JSON;
  33. const dialectSupportsJsonOperations = () => sequelize.dialect.supports.jsonOperations;
  34. const dialectSupportsJsonQuotedExtraction = () => sequelize.dialect.supports.jsonExtraction.quoted;
  35. const dialectSupportsJsonUnquotedExtraction = () =>
  36. sequelize.dialect.supports.jsonExtraction.unquoted;
  37. interface SomeInterface {
  38. foo: string;
  39. }
  40. class TestModel extends Model<InferAttributes<TestModel>> {
  41. declare intAttr1: number;
  42. declare intAttr2: number;
  43. declare nullableIntAttr: number | null;
  44. declare intArrayAttr: number[];
  45. declare intRangeAttr: Range<number>;
  46. declare dateRangeAttr: Range<Date>;
  47. declare stringAttr: string;
  48. declare binaryAttr: Buffer;
  49. declare dateAttr: Date;
  50. declare booleanAttr: boolean;
  51. declare bigIntAttr: bigint;
  52. declare jsonAttr: object | null;
  53. declare jsonbAttr: object | null;
  54. declare aliasedInt: number;
  55. declare aliasedJsonAttr: object;
  56. declare aliasedJsonbAttr: object;
  57. declare jsonbTypeLiteralAttr: { foo: string };
  58. declare jsonbInterfaceAttr: SomeInterface;
  59. declare uuidAttr: string;
  60. }
  61. type TestModelWhere = WhereOptions<Attributes<TestModel>>;
  62. describe(getTestDialectTeaser('SQL'), () => {
  63. before(() => {
  64. TestModel.init(
  65. {
  66. intAttr1: DataTypes.INTEGER,
  67. intAttr2: DataTypes.INTEGER,
  68. nullableIntAttr: DataTypes.INTEGER,
  69. ...(dialectSupportsArray() && {
  70. intArrayAttr: DataTypes.ARRAY(DataTypes.INTEGER),
  71. intRangeAttr: DataTypes.RANGE(DataTypes.INTEGER),
  72. dateRangeAttr: DataTypes.RANGE(DataTypes.DATE(3)),
  73. }),
  74. stringAttr: DataTypes.STRING,
  75. binaryAttr: DataTypes.BLOB,
  76. dateAttr: DataTypes.DATE(3),
  77. booleanAttr: DataTypes.BOOLEAN,
  78. ...(dialectSupportsBigInt() && { bigIntAttr: DataTypes.BIGINT }),
  79. aliasedInt: { type: DataTypes.INTEGER, field: 'aliased_int' },
  80. ...(dialectSupportsJson() && {
  81. jsonAttr: { type: DataTypes.JSON },
  82. aliasedJsonAttr: { type: DataTypes.JSON, field: 'aliased_json' },
  83. }),
  84. ...(dialectSupportsJsonB() && {
  85. jsonbAttr: { type: DataTypes.JSONB },
  86. aliasedJsonbAttr: { type: DataTypes.JSONB, field: 'aliased_jsonb' },
  87. jsonbTypeLiteralAttr: { type: DataTypes.JSONB },
  88. jsonbInterfaceAttr: { type: DataTypes.JSONB },
  89. }),
  90. uuidAttr: DataTypes.UUID,
  91. },
  92. { sequelize },
  93. );
  94. });
  95. describe('whereQuery', () => {
  96. it('prefixes its output with WHERE when it is not empty', () => {
  97. expectsql(queryGen.whereQuery({ firstName: 'abc' }), {
  98. default: `WHERE [firstName] = 'abc'`,
  99. mssql: `WHERE [firstName] = N'abc'`,
  100. });
  101. });
  102. it('returns an empty string if the input results in an empty query', () => {
  103. expectsql(queryGen.whereQuery({ firstName: { [Op.notIn]: [] } }), {
  104. default: '',
  105. });
  106. });
  107. });
  108. describe('whereItemsQuery', () => {
  109. type IncludesType<Haystack, Needle> = Needle extends any
  110. ? Extract<Haystack, Needle> extends never
  111. ? false
  112. : true
  113. : never;
  114. /**
  115. * 'OperatorsSupportingSequelizeValueMethods' lists all operators
  116. * that accept values: `col()`, `literal()`, `fn()`, `cast()`, and { [Op.col] }
  117. */
  118. type OperatorsSupportingSequelizeValueMethods = keyof {
  119. [Key in keyof WhereOperators<number> as IncludesType<
  120. WhereOperators<number>[Key],
  121. Col | Literal | Fn | Cast | { [Op.col]: string }
  122. > extends true
  123. ? Key
  124. : never]: WhereOperators<number>[Key];
  125. };
  126. /**
  127. * Tests whether an operator is compatible with the 5 sequelize methods that can be used as values:
  128. * - col()
  129. * - literal()
  130. * - fn()
  131. * - cast()
  132. * - legacy Op.col
  133. *
  134. * If there is a typescript error on the operator passed to this function, then
  135. * the typings in {@link WhereOperators} for the provided operator are incorrect.
  136. *
  137. * @param operator
  138. * @param sqlOperator
  139. */
  140. function testSequelizeValueMethods(
  141. operator: OperatorsSupportingSequelizeValueMethods,
  142. sqlOperator: string,
  143. ): void {
  144. testSql(
  145. { intAttr1: { [operator]: { [Op.col]: 'intAttr2' } } },
  146. {
  147. default: `[intAttr1] ${sqlOperator} [intAttr2]`,
  148. },
  149. );
  150. testSql(
  151. { intAttr1: { [operator]: col('intAttr2') } },
  152. {
  153. default: `[intAttr1] ${sqlOperator} [intAttr2]`,
  154. },
  155. );
  156. testSql(
  157. { intAttr1: { [operator]: literal('literal') } },
  158. {
  159. default: `[intAttr1] ${sqlOperator} literal`,
  160. },
  161. );
  162. testSql(
  163. { intAttr1: { [operator]: fn('NOW') } },
  164. {
  165. default: `[intAttr1] ${sqlOperator} NOW()`,
  166. },
  167. );
  168. testSql(
  169. { intAttr1: { [operator]: fn('SUM', { [Op.col]: 'intAttr2' }) } },
  170. {
  171. default: `[intAttr1] ${sqlOperator} SUM([intAttr2])`,
  172. },
  173. );
  174. testSql(
  175. { intAttr1: { [operator]: cast(col('intAttr2'), 'string') } },
  176. {
  177. default: `[intAttr1] ${sqlOperator} CAST([intAttr2] AS STRING)`,
  178. },
  179. );
  180. testSql(
  181. { intAttr1: { [operator]: cast({ [Op.col]: 'intAttr2' }, 'string') } },
  182. {
  183. default: `[intAttr1] ${sqlOperator} CAST([intAttr2] AS STRING)`,
  184. },
  185. );
  186. testSql(
  187. { intAttr1: { [operator]: cast(12, 'string') } },
  188. {
  189. default: `[intAttr1] ${sqlOperator} CAST(12 AS STRING)`,
  190. },
  191. );
  192. }
  193. /**
  194. * 'OperatorsSupportingSequelizeValueMethods' lists all operators
  195. * that accept values: `col()`, `literal()`, `fn()`, `cast()`, and { [Op.col] }
  196. */
  197. type OperatorsSupportingAnyAll<AttributeType> = keyof {
  198. [Key in keyof WhereOperators<AttributeType> as IncludesType<
  199. WhereOperators<AttributeType>[Key],
  200. | { [Op.all]: any[] | Literal | { [Op.values]: any[] } }
  201. | { [Op.any]: any[] | Literal | { [Op.values]: any[] } }
  202. > extends true
  203. ? Key
  204. : never]: WhereOperators<AttributeType>[Key];
  205. };
  206. /**
  207. * Tests whether an operator is compatible with:
  208. * - Op.any (+ Op.values)
  209. * - Op.all (+ Op.values)
  210. *
  211. * If there is a typescript error on the operator passed to this function, then
  212. * the typings in {@link WhereOperators} for the provided operator are incorrect.
  213. *
  214. * @param operator
  215. * @param sqlOperator
  216. * @param testWithValues
  217. * @param attributeName
  218. */
  219. function testSupportsAnyAll<TestWithValue>(
  220. operator: OperatorsSupportingAnyAll<TestWithValue>,
  221. sqlOperator: string,
  222. testWithValues: TestWithValue[],
  223. attributeName: AttributeNames<TestModel> = 'intAttr1',
  224. ) {
  225. if (!dialectSupportsArray()) {
  226. return;
  227. }
  228. const arrayOperators: Array<[jsOp: symbol, sqlOp: string]> = [
  229. [Op.any, 'ANY'],
  230. [Op.all, 'ALL'],
  231. ];
  232. for (const [arrayOperator, arraySqlOperator] of arrayOperators) {
  233. testSql(
  234. { [attributeName]: { [operator]: { [arrayOperator]: testWithValues } } },
  235. {
  236. default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}])`,
  237. postgres: `"${attributeName}" ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}]${attributeName === 'stringAttr' ? '::VARCHAR(255)[]' : ''})`,
  238. },
  239. );
  240. testSql(
  241. { [attributeName]: { [operator]: { [arrayOperator]: literal('literal') } } },
  242. {
  243. default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (literal)`,
  244. },
  245. );
  246. // e.g. "col" LIKE ANY (VALUES ("col2"))
  247. testSql(
  248. {
  249. [attributeName]: {
  250. [operator]: {
  251. [arrayOperator]: {
  252. [Op.values]: [
  253. literal('literal'),
  254. fn('UPPER', col('col2')),
  255. col('col3'),
  256. cast(col('col'), 'string'),
  257. testWithValues[0],
  258. ],
  259. },
  260. },
  261. },
  262. },
  263. {
  264. default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (VALUES (literal), (UPPER("col2")), ("col3"), (CAST("col" AS STRING)), (${util.inspect(testWithValues[0])}))`,
  265. },
  266. );
  267. }
  268. }
  269. const testSql = createTester(
  270. (it, whereObj: TestModelWhere, expectations: Expectations, options?: FormatWhereOptions) => {
  271. it(
  272. util.inspect(whereObj, { depth: 10 }) + (options ? `, ${util.inspect(options)}` : ''),
  273. () => {
  274. const sqlOrError = attempt(() =>
  275. queryGen.whereItemsQuery(whereObj, {
  276. ...options,
  277. model: TestModel,
  278. }),
  279. );
  280. return expectsql(sqlOrError, expectations);
  281. },
  282. );
  283. },
  284. );
  285. // "where" is typically optional. If the user sets it to undefined, we treat is as if the option was not set.
  286. testSql(undefined, {
  287. default: '',
  288. });
  289. testSql(
  290. {},
  291. {
  292. default: '',
  293. },
  294. );
  295. testSql([], {
  296. default: '',
  297. });
  298. // @ts-expect-error -- not supported, testing that it throws
  299. testSql(null, {
  300. default:
  301. new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts.
  302. Value: null
  303. Caused by: Invalid Query: expected a plain object, an array or a sequelize SQL method but got null`),
  304. });
  305. // @ts-expect-error -- not supported, testing that it throws
  306. testSql(10, {
  307. default:
  308. new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts.
  309. Value: 10
  310. Caused by: Invalid Query: expected a plain object, an array or a sequelize SQL method but got 10`),
  311. });
  312. testSql(
  313. { intAttr1: undefined },
  314. {
  315. default:
  316. new Error(`Invalid value received for the "where" option. Refer to the sequelize documentation to learn which values the "where" option accepts.
  317. Value: { intAttr1: undefined }
  318. Caused by: "undefined" cannot be escaped`),
  319. },
  320. );
  321. testSql(
  322. // @ts-expect-error -- user does not exist
  323. { intAttr1: 1, user: undefined },
  324. { default: new Error('"undefined" cannot be escaped') },
  325. );
  326. testSql(
  327. { intAttr1: 1 },
  328. {
  329. default: '[User].[intAttr1] = 1',
  330. },
  331. { mainAlias: 'User' },
  332. );
  333. testSql(
  334. { dateAttr: { $gte: '2022-11-06' } },
  335. { default: new Error(`{ '$gte': '2022-11-06' } is not a valid date`) },
  336. );
  337. testSql(literal('raw sql'), {
  338. default: 'raw sql',
  339. });
  340. describe('value serialization', () => {
  341. // string
  342. testSql(
  343. { stringAttr: '1' },
  344. {
  345. default: `[stringAttr] = '1'`,
  346. mssql: `[stringAttr] = N'1'`,
  347. },
  348. );
  349. testSql(
  350. {
  351. stringAttr: 'here is a null char: \0',
  352. },
  353. {
  354. default: "[stringAttr] = 'here is a null char: \\0'",
  355. snowflake: '"stringAttr" = \'here is a null char: \0\'',
  356. mssql: "[stringAttr] = N'here is a null char: \0'",
  357. db2: '"stringAttr" = \'here is a null char: \0\'',
  358. ibmi: '"stringAttr" = \'here is a null char: \0\'',
  359. sqlite3: "`stringAttr` = 'here is a null char: \0'",
  360. },
  361. );
  362. testSql(
  363. {
  364. dateAttr: 1_356_998_400_000,
  365. },
  366. {
  367. default: `[dateAttr] = '2013-01-01 00:00:00.000 +00:00'`,
  368. 'mariadb mysql': `\`dateAttr\` = '2013-01-01 00:00:00.000'`,
  369. mssql: `[dateAttr] = N'2013-01-01 00:00:00.000 +00:00'`,
  370. 'db2 snowflake ibmi': `"dateAttr" = '2013-01-01 00:00:00.000'`,
  371. },
  372. );
  373. describe('Buffer', () => {
  374. testSql(
  375. { binaryAttr: Buffer.from('Sequelize') },
  376. {
  377. ibmi: `"binaryAttr" = BLOB(X'53657175656c697a65')`,
  378. postgres: `"binaryAttr" = '\\x53657175656c697a65'`,
  379. 'sqlite3 mariadb mysql': "`binaryAttr` = X'53657175656c697a65'",
  380. db2: `"binaryAttr" = BLOB('Sequelize')`,
  381. snowflake: `"binaryAttr" = X'53657175656c697a65'`,
  382. mssql: '[binaryAttr] = 0x53657175656c697a65',
  383. },
  384. );
  385. // Including a quote (') to ensure dialects that don't convert to hex are safe from SQL injection.
  386. testSql(
  387. { binaryAttr: [Buffer.from(`Seque'lize1`), Buffer.from('Sequelize2')] },
  388. {
  389. ibmi: `"binaryAttr" IN (BLOB(X'5365717565276c697a6531'), BLOB(X'53657175656c697a6532'))`,
  390. postgres: `"binaryAttr" IN ('\\x5365717565276c697a6531', '\\x53657175656c697a6532')`,
  391. 'sqlite3 mariadb mysql':
  392. "`binaryAttr` IN (X'5365717565276c697a6531', X'53657175656c697a6532')",
  393. db2: `"binaryAttr" IN (BLOB('Seque''lize1'), BLOB('Sequelize2'))`,
  394. snowflake: `"binaryAttr" IN (X'5365717565276c697a6531', X'53657175656c697a6532')`,
  395. mssql: '[binaryAttr] IN (0x5365717565276c697a6531, 0x53657175656c697a6532)',
  396. },
  397. );
  398. });
  399. });
  400. describe('implicit operator', () => {
  401. testSql(
  402. { intAttr1: 1 },
  403. {
  404. default: '[intAttr1] = 1',
  405. },
  406. );
  407. testSql(
  408. { stringAttr: '1' },
  409. {
  410. default: `[stringAttr] = '1'`,
  411. mssql: `[stringAttr] = N'1'`,
  412. },
  413. );
  414. testSql(
  415. { intAttr1: [1, 2] },
  416. {
  417. default: '[intAttr1] IN (1, 2)',
  418. },
  419. );
  420. testSql(
  421. { stringAttr: ['1', '2'] },
  422. {
  423. default: `[stringAttr] IN ('1', '2')`,
  424. mssql: `[stringAttr] IN (N'1', N'2')`,
  425. },
  426. );
  427. testSql(
  428. { intAttr1: ['not-an-int'] },
  429. { default: new Error(`'not-an-int' is not a valid integer`) },
  430. );
  431. testSql(
  432. { 'stringAttr::integer': 1 },
  433. {
  434. default: 'CAST([stringAttr] AS INTEGER) = 1',
  435. },
  436. );
  437. testSql(
  438. { $intAttr1$: 1 },
  439. {
  440. default: '[intAttr1] = 1',
  441. },
  442. );
  443. testSql(
  444. { '$stringAttr$::integer': 1 },
  445. {
  446. default: 'CAST([stringAttr] AS INTEGER) = 1',
  447. },
  448. );
  449. testSql(
  450. { '$association.attribute$': 1 },
  451. {
  452. default: '[association].[attribute] = 1',
  453. },
  454. );
  455. testSql(
  456. { '$association.attribute$::integer': 1 },
  457. {
  458. default: 'CAST([association].[attribute] AS INTEGER) = 1',
  459. },
  460. );
  461. testSql(
  462. { booleanAttr: true },
  463. {
  464. default: `[booleanAttr] = true`,
  465. mssql: '[booleanAttr] = 1',
  466. sqlite3: '`booleanAttr` = 1',
  467. ibmi: '"booleanAttr" = 1',
  468. },
  469. );
  470. testSql(
  471. {
  472. stringAttr: 'a project',
  473. intAttr1: {
  474. [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }],
  475. },
  476. },
  477. {
  478. default: "[stringAttr] = 'a project' AND ([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10)",
  479. mssql: "[stringAttr] = N'a project' AND ([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10)",
  480. },
  481. );
  482. testSql(
  483. { nullableIntAttr: null },
  484. {
  485. default: '[nullableIntAttr] IS NULL',
  486. },
  487. );
  488. testSql(
  489. { nullableIntAttr: SQL_NULL },
  490. {
  491. default: '[nullableIntAttr] IS NULL',
  492. },
  493. );
  494. testSql(
  495. { dateAttr: new Date('2021-01-01T00:00:00Z') },
  496. {
  497. default: `[dateAttr] = '2021-01-01 00:00:00.000 +00:00'`,
  498. mssql: `[dateAttr] = N'2021-01-01 00:00:00.000 +00:00'`,
  499. 'mariadb mysql': `\`dateAttr\` = '2021-01-01 00:00:00.000'`,
  500. 'db2 ibmi snowflake': `"dateAttr" = '2021-01-01 00:00:00.000'`,
  501. },
  502. );
  503. testSql(
  504. { intAttr1: { [Op.col]: 'intAttr2' } },
  505. {
  506. default: '[intAttr1] = [intAttr2]',
  507. },
  508. );
  509. testSql(
  510. { intAttr1: col('intAttr2') },
  511. {
  512. default: '[intAttr1] = [intAttr2]',
  513. },
  514. );
  515. testSql(
  516. { intAttr1: literal('literal') },
  517. {
  518. default: '[intAttr1] = literal',
  519. },
  520. );
  521. testSql(
  522. { stringAttr: fn('UPPER', col('stringAttr')) },
  523. {
  524. default: '[stringAttr] = UPPER([stringAttr])',
  525. },
  526. );
  527. testSql(
  528. { stringAttr: fn('UPPER', { [Op.col]: 'stringAttr' }) },
  529. {
  530. default: '[stringAttr] = UPPER([stringAttr])',
  531. },
  532. );
  533. testSql(
  534. { stringAttr: cast(col('intAttr1'), 'string') },
  535. {
  536. default: '[stringAttr] = CAST([intAttr1] AS STRING)',
  537. },
  538. );
  539. testSql(
  540. { stringAttr: cast({ [Op.col]: 'intAttr1' }, 'string') },
  541. {
  542. default: '[stringAttr] = CAST([intAttr1] AS STRING)',
  543. },
  544. );
  545. testSql(
  546. { stringAttr: cast('abc', 'string') },
  547. {
  548. default: `[stringAttr] = CAST('abc' AS STRING)`,
  549. mssql: `[stringAttr] = CAST(N'abc' AS STRING)`,
  550. },
  551. );
  552. if (dialectSupportsArray()) {
  553. testSql(
  554. { intArrayAttr: [1, 2] },
  555. {
  556. default: `[intArrayAttr] = ARRAY[1,2]`,
  557. },
  558. );
  559. testSql(
  560. { intArrayAttr: [] },
  561. {
  562. default: `[intArrayAttr] = ARRAY[]::INTEGER[]`,
  563. },
  564. );
  565. // when using arrays, Op.in is never included
  566. testSql(
  567. // @ts-expect-error -- Omitting the operator with an array attribute is always Op.eq, never Op.in
  568. { intArrayAttr: [[1, 2]] },
  569. { default: new Error('[ 1, 2 ] is not a valid integer') },
  570. );
  571. testSql(
  572. { intAttr1: { [Op.any]: [2, 3, 4] } },
  573. {
  574. default: '[intAttr1] = ANY (ARRAY[2,3,4])',
  575. },
  576. );
  577. testSql(
  578. { intAttr1: { [Op.any]: literal('literal') } },
  579. {
  580. default: '[intAttr1] = ANY (literal)',
  581. },
  582. );
  583. testSql(
  584. { intAttr1: { [Op.any]: { [Op.values]: [col('col')] } } },
  585. {
  586. default: '[intAttr1] = ANY (VALUES ([col]))',
  587. },
  588. );
  589. testSql(
  590. { intAttr1: { [Op.all]: [2, 3, 4] } },
  591. {
  592. default: '[intAttr1] = ALL (ARRAY[2,3,4])',
  593. },
  594. );
  595. testSql(
  596. { intAttr1: { [Op.all]: literal('literal') } },
  597. {
  598. default: '[intAttr1] = ALL (literal)',
  599. },
  600. );
  601. testSql(
  602. { intAttr1: { [Op.all]: { [Op.values]: [col('col')] } } },
  603. {
  604. default: '[intAttr1] = ALL (VALUES ([col]))',
  605. },
  606. );
  607. // e.g. "col" LIKE ANY (VALUES ("col2"))
  608. testSql(
  609. {
  610. intAttr1: {
  611. [Op.any]: {
  612. [Op.values]: [
  613. literal('literal'),
  614. fn('UPPER', col('col2')),
  615. col('col3'),
  616. cast(col('col'), 'string'),
  617. 1,
  618. ],
  619. },
  620. },
  621. },
  622. {
  623. default: `[intAttr1] = ANY (VALUES (literal), (UPPER([col2])), ([col3]), (CAST([col] AS STRING)), (1))`,
  624. },
  625. );
  626. }
  627. });
  628. describe('Op.eq', () => {
  629. testSql(
  630. { intAttr1: { [Op.eq]: 1 } },
  631. {
  632. default: '[intAttr1] = 1',
  633. },
  634. );
  635. testSql(
  636. { 'intAttr1::integer': { [Op.eq]: 1 } },
  637. {
  638. default: 'CAST([intAttr1] AS INTEGER) = 1',
  639. },
  640. );
  641. testSql(
  642. { $intAttr1$: { [Op.eq]: 1 } },
  643. {
  644. default: '[intAttr1] = 1',
  645. },
  646. );
  647. testSql(
  648. { '$intAttr1$::integer': { [Op.eq]: 1 } },
  649. {
  650. default: 'CAST([intAttr1] AS INTEGER) = 1',
  651. },
  652. );
  653. testSql(
  654. { '$association.attribute$': { [Op.eq]: 1 } },
  655. {
  656. default: '[association].[attribute] = 1',
  657. },
  658. );
  659. testSql(
  660. { '$association.attribute$::integer': { [Op.eq]: 1 } },
  661. {
  662. default: `CAST([association].[attribute] AS INTEGER) = 1`,
  663. },
  664. );
  665. if (dialectSupportsArray()) {
  666. // @ts-expect-error -- intArrayAttr is not an array
  667. const ignore: TestModelWhere = { intAttr1: { [Op.eq]: [1, 2] } };
  668. testSql(
  669. { intArrayAttr: { [Op.eq]: [1, 2] } },
  670. {
  671. default: '[intArrayAttr] = ARRAY[1,2]',
  672. },
  673. );
  674. }
  675. {
  676. // @ts-expect-error -- intAttr1 is not nullable
  677. const ignore: TestModelWhere = { intAttr1: { [Op.eq]: null } };
  678. // this one is
  679. testSql(
  680. { nullableIntAttr: { [Op.eq]: null } },
  681. {
  682. default: '[nullableIntAttr] IS NULL',
  683. },
  684. );
  685. }
  686. testSql(
  687. { booleanAttr: { [Op.eq]: true } },
  688. {
  689. default: '[booleanAttr] = true',
  690. 'mssql sqlite3 ibmi': '[booleanAttr] = 1',
  691. },
  692. );
  693. testSequelizeValueMethods(Op.eq, '=');
  694. testSupportsAnyAll(Op.eq, '=', [2, 3, 4]);
  695. });
  696. describe('Op.ne', () => {
  697. testSql(
  698. { intAttr1: { [Op.ne]: 1 } },
  699. {
  700. default: '[intAttr1] != 1',
  701. },
  702. );
  703. if (dialectSupportsArray()) {
  704. testSql(
  705. { intArrayAttr: { [Op.ne]: [1, 2] } },
  706. {
  707. default: '[intArrayAttr] != ARRAY[1,2]',
  708. },
  709. );
  710. }
  711. testSql(
  712. { nullableIntAttr: { [Op.ne]: null } },
  713. {
  714. default: '[nullableIntAttr] IS NOT NULL',
  715. },
  716. );
  717. testSql(
  718. { booleanAttr: { [Op.ne]: true } },
  719. {
  720. default: '[booleanAttr] != true',
  721. 'mssql ibmi sqlite3': '[booleanAttr] != 1',
  722. },
  723. );
  724. testSequelizeValueMethods(Op.ne, '!=');
  725. testSupportsAnyAll(Op.ne, '!=', [2, 3, 4]);
  726. });
  727. describe('Op.is', () => {
  728. {
  729. // @ts-expect-error -- intAttr is not nullable
  730. const ignore: TestModelWhere = { intAttr: { [Op.is]: null } };
  731. }
  732. {
  733. // @ts-expect-error -- stringAttr is not a boolean
  734. const ignore: TestModelWhere = { stringAttr: { [Op.is]: true } };
  735. }
  736. testSql(
  737. { nullableIntAttr: { [Op.is]: null } },
  738. {
  739. default: '[nullableIntAttr] IS NULL',
  740. },
  741. );
  742. testSql(
  743. { nullableIntAttr: { [Op.is]: SQL_NULL } },
  744. {
  745. default: '[nullableIntAttr] IS NULL',
  746. },
  747. );
  748. testSql(
  749. { booleanAttr: { [Op.is]: false } },
  750. {
  751. default: '[booleanAttr] IS false',
  752. 'mssql ibmi sqlite3': '[booleanAttr] IS 0',
  753. },
  754. );
  755. testSql(
  756. { booleanAttr: { [Op.is]: true } },
  757. {
  758. default: '[booleanAttr] IS true',
  759. 'mssql ibmi sqlite3': '[booleanAttr] IS 1',
  760. },
  761. );
  762. testSql(
  763. // @ts-expect-error -- not supported, testing that it throws
  764. { intAttr1: { [Op.is]: 1 } },
  765. {
  766. default: new Error(
  767. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  768. ),
  769. },
  770. );
  771. testSql(
  772. // @ts-expect-error -- not supported, testing that it throws
  773. { intAttr1: { [Op.is]: { [Op.col]: 'intAttr2' } } },
  774. {
  775. default: new Error(
  776. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  777. ),
  778. },
  779. );
  780. testSql(
  781. // @ts-expect-error -- not supported, testing that it throws
  782. { intAttr1: { [Op.is]: col('intAttr2') } },
  783. {
  784. default: new Error(
  785. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  786. ),
  787. },
  788. );
  789. testSql(
  790. { intAttr1: { [Op.is]: literal('UNKNOWN') } },
  791. {
  792. default: '[intAttr1] IS UNKNOWN',
  793. },
  794. );
  795. testSql(
  796. // @ts-expect-error -- not supported, testing that it throws
  797. { intAttr1: { [Op.is]: fn('UPPER', col('intAttr2')) } },
  798. {
  799. default: new Error(
  800. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  801. ),
  802. },
  803. );
  804. testSql(
  805. // @ts-expect-error -- not supported, testing that it throws
  806. { intAttr1: { [Op.is]: cast(col('intAttr2'), 'boolean') } },
  807. {
  808. default: new Error(
  809. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  810. ),
  811. },
  812. );
  813. if (dialectSupportsArray()) {
  814. testSql(
  815. // @ts-expect-error -- not supported, testing that it throws
  816. { intAttr1: { [Op.is]: { [Op.any]: [2, 3] } } },
  817. {
  818. default: new Error(
  819. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  820. ),
  821. },
  822. );
  823. testSql(
  824. // @ts-expect-error -- not supported, testing that it throws
  825. { intAttr1: { [Op.is]: { [Op.all]: [2, 3, 4] } } },
  826. {
  827. default: new Error(
  828. 'Operators Op.is and Op.isNot can only be used with null, true, false or a literal.',
  829. ),
  830. },
  831. );
  832. }
  833. });
  834. describe('Op.isNot', () => {
  835. testSql(
  836. { nullableIntAttr: { [Op.isNot]: null } },
  837. {
  838. default: '[nullableIntAttr] IS NOT NULL',
  839. },
  840. );
  841. testSql(
  842. { booleanAttr: { [Op.isNot]: false } },
  843. {
  844. default: '[booleanAttr] IS NOT false',
  845. 'mssql ibmi sqlite3': '[booleanAttr] IS NOT 0',
  846. },
  847. );
  848. testSql(
  849. { booleanAttr: { [Op.isNot]: true } },
  850. {
  851. default: '[booleanAttr] IS NOT true',
  852. 'mssql ibmi sqlite3': '[booleanAttr] IS NOT 1',
  853. },
  854. );
  855. });
  856. describe('Op.not', () => {
  857. testSql(
  858. { [Op.not]: {} },
  859. {
  860. default: '',
  861. },
  862. );
  863. testSql(
  864. {
  865. [Op.not]: {
  866. [Op.not]: {},
  867. },
  868. },
  869. {
  870. default: '',
  871. },
  872. );
  873. testSql(
  874. { [Op.not]: [] },
  875. {
  876. default: '',
  877. },
  878. );
  879. testSql(
  880. { nullableIntAttr: { [Op.not]: {} } },
  881. {
  882. default: '',
  883. },
  884. );
  885. testSql(
  886. { nullableIntAttr: { [Op.not]: null } },
  887. {
  888. default: 'NOT ([nullableIntAttr] IS NULL)',
  889. },
  890. );
  891. testSql(
  892. { booleanAttr: { [Op.not]: false } },
  893. {
  894. default: 'NOT ([booleanAttr] = false)',
  895. mssql: 'NOT ([booleanAttr] = 0)',
  896. ibmi: 'NOT ("booleanAttr" = 0)',
  897. sqlite3: 'NOT (`booleanAttr` = 0)',
  898. },
  899. );
  900. testSql(
  901. { booleanAttr: { [Op.not]: true } },
  902. {
  903. default: 'NOT ([booleanAttr] = true)',
  904. mssql: 'NOT ([booleanAttr] = 1)',
  905. ibmi: 'NOT ("booleanAttr" = 1)',
  906. sqlite3: 'NOT (`booleanAttr` = 1)',
  907. },
  908. );
  909. testSql(
  910. { intAttr1: { [Op.not]: 1 } },
  911. {
  912. default: 'NOT ([intAttr1] = 1)',
  913. },
  914. );
  915. testSql(
  916. { intAttr1: { [Op.not]: [1, 2] } },
  917. {
  918. default: 'NOT ([intAttr1] IN (1, 2))',
  919. },
  920. );
  921. {
  922. // @ts-expect-error -- not a valid query: attribute does not exist.
  923. const ignore: TestModelWhere = { [Op.not]: { doesNotExist: 5 } };
  924. }
  925. testSql(
  926. { [Op.not]: { intAttr1: 5 } },
  927. {
  928. default: 'NOT ([intAttr1] = 5)',
  929. },
  930. );
  931. testSql(
  932. { [Op.not]: { intAttr1: { [Op.gt]: 5 } } },
  933. {
  934. default: 'NOT ([intAttr1] > 5)',
  935. },
  936. );
  937. testSql(
  938. { [Op.not]: where(col('intAttr1'), Op.eq, '5') },
  939. {
  940. default: `NOT ([intAttr1] = '5')`,
  941. mssql: `NOT ([intAttr1] = N'5')`,
  942. },
  943. );
  944. if (dialectSupportsJsonOperations() && dialectSupportsJsonQuotedExtraction()) {
  945. testSql(
  946. { [Op.not]: json('data.key', 10) },
  947. {
  948. postgres: `NOT ("data"->'key' = '10')`,
  949. sqlite3: `NOT (json_extract(\`data\`,'$.key') = '10')`,
  950. mariadb: `NOT (json_compact(json_extract(\`data\`,'$.key')) = '10')`,
  951. mysql: `NOT (json_extract(\`data\`,'$.key') = CAST('10' AS JSON))`,
  952. },
  953. );
  954. }
  955. testSql(
  956. { intAttr1: { [Op.not]: { [Op.gt]: 5 } } },
  957. {
  958. default: 'NOT ([intAttr1] > 5)',
  959. },
  960. );
  961. });
  962. function describeComparisonSuite(
  963. operator: typeof Op.gt | typeof Op.gte | typeof Op.lt | typeof Op.lte,
  964. sqlOperator: string,
  965. ) {
  966. // ensure gte, gt, lte, lt support the same typings, so we only have to test their typings once.
  967. // unfortunately, at time of writing (TS 4.5.5), TypeScript
  968. // does not detect an error in `{ [operator]: null }`
  969. // but it does detect an error in { [Op.gt]: null }`
  970. expectTypeOf<WhereOperators[typeof Op.gte]>().toEqualTypeOf<WhereOperators[typeof Op.gt]>();
  971. expectTypeOf<WhereOperators[typeof Op.lt]>().toEqualTypeOf<WhereOperators[typeof Op.gt]>();
  972. expectTypeOf<WhereOperators[typeof Op.lte]>().toEqualTypeOf<WhereOperators[typeof Op.gt]>();
  973. describe(`Op.${operator.description}`, () => {
  974. {
  975. const ignore: TestModelWhere = { intAttr1: { [Op.gt]: 1 } };
  976. testSql(
  977. { intAttr1: { [operator]: 1 } },
  978. {
  979. default: `[intAttr1] ${sqlOperator} 1`,
  980. },
  981. );
  982. }
  983. {
  984. const ignore: TestModelWhere = { stringAttr: { [Op.gt]: 'abc' } };
  985. testSql(
  986. { stringAttr: { [operator]: 'abc' } },
  987. {
  988. default: `[stringAttr] ${sqlOperator} 'abc'`,
  989. mssql: `[stringAttr] ${sqlOperator} N'abc'`,
  990. },
  991. );
  992. }
  993. if (dialectSupportsArray()) {
  994. const ignore: TestModelWhere = { intArrayAttr: { [Op.gt]: [1, 2] } };
  995. testSql(
  996. { intArrayAttr: { [operator]: [1, 2] } },
  997. {
  998. default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2]`,
  999. },
  1000. );
  1001. }
  1002. expectTypeOf({ intAttr1: { [Op.gt]: null } }).not.toMatchTypeOf<WhereOperators>();
  1003. testSql(
  1004. { intAttr1: { [operator]: null } },
  1005. {
  1006. default: `[intAttr1] ${sqlOperator} NULL`,
  1007. },
  1008. );
  1009. testSequelizeValueMethods(operator, sqlOperator);
  1010. testSupportsAnyAll(operator, sqlOperator, [2, 3, 4]);
  1011. });
  1012. }
  1013. describeComparisonSuite(Op.gt, '>');
  1014. describeComparisonSuite(Op.gte, '>=');
  1015. describeComparisonSuite(Op.lt, '<');
  1016. describeComparisonSuite(Op.lte, '<=');
  1017. function describeBetweenSuite(
  1018. operator: typeof Op.between | typeof Op.notBetween,
  1019. sqlOperator: string,
  1020. ) {
  1021. // ensure between and notBetween support the same typings, so we only have to test their typings once.
  1022. // unfortunately, at time of writing (TS 4.5.5), TypeScript
  1023. // does not detect an error in `{ [operator]: null }`
  1024. // but it does detect an error in { [Op.gt]: null }`
  1025. expectTypeOf<WhereOperators[typeof Op.between]>().toEqualTypeOf<
  1026. WhereOperators[typeof Op.notBetween]
  1027. >();
  1028. describe(`Op.${operator.description}`, () => {
  1029. expectTypeOf({ id: { [Op.between]: [1, 2] } }).toMatchTypeOf<TestModelWhere>();
  1030. expectTypeOf({
  1031. id: { [Op.between]: [new Date(), new Date()] },
  1032. }).toMatchTypeOf<TestModelWhere>();
  1033. expectTypeOf({ id: { [Op.between]: ['a', 'b'] } }).toMatchTypeOf<TestModelWhere>();
  1034. // expectTypeOf doesn't work with this one:
  1035. {
  1036. const ignoreRight: TestModelWhere = {
  1037. intAttr1: { [Op.between]: [1, 2] },
  1038. };
  1039. testSql(
  1040. { intAttr1: { [operator]: [1, 2] } },
  1041. {
  1042. default: `[intAttr1] ${sqlOperator} 1 AND 2`,
  1043. },
  1044. );
  1045. // @ts-expect-error -- must pass exactly 2 items
  1046. const ignoreWrong: TestModelWhere = { intAttr1: { [Op.between]: [1, 2, 3] } };
  1047. // @ts-expect-error -- must pass exactly 2 items
  1048. const ignoreWrong2: TestModelWhere = { intAttr1: { [Op.between]: [1] } };
  1049. testSql(
  1050. { intAttr1: { [operator]: [1] } },
  1051. {
  1052. default: new Error(
  1053. 'Operators Op.between and Op.notBetween must be used with an array of two values, or a literal.',
  1054. ),
  1055. },
  1056. );
  1057. // @ts-expect-error -- must pass exactly 2 items
  1058. const ignoreWrong3: TestModelWhere = { intAttr1: { [Op.between]: [] } };
  1059. }
  1060. if (dialectSupportsArray()) {
  1061. {
  1062. const ignoreRight: TestModelWhere = {
  1063. intArrayAttr: {
  1064. [Op.between]: [
  1065. [1, 2],
  1066. [3, 4],
  1067. ],
  1068. },
  1069. };
  1070. testSql(
  1071. {
  1072. intArrayAttr: {
  1073. [operator]: [
  1074. [1, 2],
  1075. [3, 4],
  1076. ],
  1077. },
  1078. },
  1079. {
  1080. default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2] AND ARRAY[3,4]`,
  1081. },
  1082. );
  1083. }
  1084. {
  1085. // @ts-expect-error -- this is not valid because intAttr1 is not an array and cannot be compared to arrays
  1086. const ignore: TestModelWhere = {
  1087. intAttr1: {
  1088. [Op.between]: [
  1089. [1, 2],
  1090. [3, 4],
  1091. ],
  1092. },
  1093. };
  1094. }
  1095. }
  1096. {
  1097. const ignoreRight: TestModelWhere = {
  1098. intAttr1: { [Op.between]: [col('col1'), col('col2')] },
  1099. };
  1100. testSql(
  1101. { intAttr1: { [operator]: [col('col1'), col('col2')] } },
  1102. {
  1103. default: `[intAttr1] ${sqlOperator} [col1] AND [col2]`,
  1104. },
  1105. );
  1106. }
  1107. {
  1108. const ignoreRight: TestModelWhere = {
  1109. intAttr1: { [Op.between]: [literal('literal1'), literal('literal2')] },
  1110. };
  1111. testSql(
  1112. { intAttr1: { [operator]: [literal('literal1'), literal('literal2')] } },
  1113. {
  1114. default: `[intAttr1] ${sqlOperator} literal1 AND literal2`,
  1115. },
  1116. );
  1117. }
  1118. {
  1119. const ignoreRight: TestModelWhere = {
  1120. intAttr1: { [Op.between]: [fn('NOW'), fn('NOW')] },
  1121. };
  1122. testSql(
  1123. { intAttr1: { [operator]: [fn('NOW'), fn('NOW')] } },
  1124. {
  1125. default: `[intAttr1] ${sqlOperator} NOW() AND NOW()`,
  1126. },
  1127. );
  1128. }
  1129. {
  1130. const ignoreRight: TestModelWhere = {
  1131. intAttr1: { [Op.between]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] },
  1132. };
  1133. testSql(
  1134. { intAttr1: { [operator]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] } },
  1135. {
  1136. default: `[intAttr1] ${sqlOperator} [col1] AND [col2]`,
  1137. },
  1138. );
  1139. }
  1140. {
  1141. const ignoreRight: TestModelWhere = {
  1142. intAttr1: { [Op.between]: [cast(col('col'), 'string'), cast(col('col'), 'string')] },
  1143. };
  1144. testSql(
  1145. { intAttr1: { [operator]: [cast(col('col'), 'string'), cast(col('col'), 'string')] } },
  1146. {
  1147. default: `[intAttr1] ${sqlOperator} CAST([col] AS STRING) AND CAST([col] AS STRING)`,
  1148. },
  1149. );
  1150. }
  1151. {
  1152. const ignoreRight: TestModelWhere = {
  1153. intAttr1: { [Op.between]: literal('literal1 AND literal2') },
  1154. };
  1155. testSql(
  1156. { intAttr1: { [operator]: literal('literal1 AND literal2') } },
  1157. {
  1158. default: `[intAttr1] ${sqlOperator} literal1 AND literal2`,
  1159. },
  1160. );
  1161. }
  1162. });
  1163. }
  1164. describeBetweenSuite(Op.between, 'BETWEEN');
  1165. describeBetweenSuite(Op.notBetween, 'NOT BETWEEN');
  1166. function describeInSuite(
  1167. operator: typeof Op.in | typeof Op.notIn,
  1168. sqlOperator: string,
  1169. extraTests: () => void,
  1170. ): void {
  1171. // ensure between and notBetween support the same typings, so we only have to test their typings once.
  1172. // unfortunately, at time of writing (TS 4.5.5), TypeScript
  1173. // does not detect an error in `{ [operator]: null }`
  1174. // but it does detect an error in { [Op.gt]: null }`
  1175. expectTypeOf<WhereOperators[typeof Op.between]>().toEqualTypeOf<
  1176. WhereOperators[typeof Op.notBetween]
  1177. >();
  1178. describe(`Op.${operator.description}`, () => {
  1179. {
  1180. const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [1, 2, 3] } };
  1181. testSql(
  1182. { intAttr1: { [operator]: [1, 2, 3] } },
  1183. {
  1184. default: `[intAttr1] ${sqlOperator} (1, 2, 3)`,
  1185. },
  1186. );
  1187. }
  1188. if (dialectSupportsArray()) {
  1189. {
  1190. // valid
  1191. const ignore: TestModelWhere = {
  1192. intArrayAttr: {
  1193. [Op.in]: [
  1194. [1, 2],
  1195. [3, 4],
  1196. ],
  1197. },
  1198. };
  1199. testSql(
  1200. {
  1201. intArrayAttr: {
  1202. [operator]: [
  1203. [1, 2],
  1204. [3, 4],
  1205. ],
  1206. },
  1207. },
  1208. {
  1209. default: `[intArrayAttr] ${sqlOperator} (ARRAY[1,2], ARRAY[3,4])`,
  1210. },
  1211. );
  1212. }
  1213. {
  1214. // @ts-expect-error -- intAttr1 is not an array
  1215. const ignore: TestModelWhere = {
  1216. intAttr1: {
  1217. [Op.in]: [
  1218. [1, 2],
  1219. [3, 4],
  1220. ],
  1221. },
  1222. };
  1223. testSql(
  1224. {
  1225. intArrayAttr: {
  1226. [operator]: [
  1227. [1, 2],
  1228. [3, 4],
  1229. ],
  1230. },
  1231. },
  1232. {
  1233. default: `[intArrayAttr] ${sqlOperator} (ARRAY[1,2], ARRAY[3,4])`,
  1234. },
  1235. );
  1236. }
  1237. }
  1238. {
  1239. // @ts-expect-error -- this is invalid because intAttr1 is not an array and cannot be compared to arrays.
  1240. const ignore: TestModelWhere = {
  1241. intAttr1: {
  1242. [Op.in]: [
  1243. [1, 2],
  1244. [3, 4],
  1245. ],
  1246. },
  1247. };
  1248. }
  1249. {
  1250. // @ts-expect-error -- not supported, testing that it throws
  1251. const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: 1 } };
  1252. testSql(
  1253. { intAttr1: { [operator]: 1 } },
  1254. {
  1255. default: new Error(
  1256. 'Operators Op.in and Op.notIn must be called with an array of values, or a literal',
  1257. ),
  1258. },
  1259. );
  1260. }
  1261. {
  1262. // @ts-expect-error -- not supported, testing that it throws
  1263. const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: col('col2') } };
  1264. testSql(
  1265. { intAttr1: { [operator]: col('col1') } },
  1266. {
  1267. default: new Error(
  1268. 'Operators Op.in and Op.notIn must be called with an array of values, or a literal',
  1269. ),
  1270. },
  1271. );
  1272. }
  1273. {
  1274. const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [col('col1'), col('col2')] } };
  1275. testSql(
  1276. { intAttr1: { [operator]: [col('col1'), col('col2')] } },
  1277. {
  1278. default: `[intAttr1] ${sqlOperator} ([col1], [col2])`,
  1279. },
  1280. );
  1281. }
  1282. {
  1283. const ignoreRight: TestModelWhere = {
  1284. intAttr1: { [Op.in]: [literal('literal1'), literal('literal2')] },
  1285. };
  1286. testSql(
  1287. { intAttr1: { [operator]: [literal('literal1'), literal('literal2')] } },
  1288. {
  1289. default: `[intAttr1] ${sqlOperator} (literal1, literal2)`,
  1290. },
  1291. );
  1292. }
  1293. {
  1294. const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: [fn('NOW'), fn('NOW')] } };
  1295. testSql(
  1296. { intAttr1: { [operator]: [fn('NOW'), fn('NOW')] } },
  1297. {
  1298. default: `[intAttr1] ${sqlOperator} (NOW(), NOW())`,
  1299. },
  1300. );
  1301. }
  1302. {
  1303. const ignoreRight: TestModelWhere = {
  1304. intAttr1: { [Op.in]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] },
  1305. };
  1306. testSql(
  1307. { intAttr1: { [operator]: [{ [Op.col]: 'col1' }, { [Op.col]: 'col2' }] } },
  1308. {
  1309. default: `[intAttr1] ${sqlOperator} ([col1], [col2])`,
  1310. },
  1311. );
  1312. }
  1313. {
  1314. const ignoreRight: TestModelWhere = {
  1315. intAttr1: { [Op.in]: [cast(col('col'), 'string'), cast(col('col'), 'string')] },
  1316. };
  1317. testSql(
  1318. { intAttr1: { [operator]: [cast(col('col'), 'string'), cast(col('col'), 'string')] } },
  1319. {
  1320. default: `[intAttr1] ${sqlOperator} (CAST([col] AS STRING), CAST([col] AS STRING))`,
  1321. },
  1322. );
  1323. }
  1324. {
  1325. const ignoreRight: TestModelWhere = { intAttr1: { [Op.in]: literal('literal') } };
  1326. testSql(
  1327. { intAttr1: { [operator]: literal('literal') } },
  1328. {
  1329. default: `[intAttr1] ${sqlOperator} literal`,
  1330. },
  1331. );
  1332. }
  1333. {
  1334. // @ts-expect-error -- Op.all is not compatible with Op.in
  1335. const ignoreWrong: TestModelWhere = { intAttr1: { [Op.in]: { [Op.all]: [] } } };
  1336. }
  1337. extraTests();
  1338. });
  1339. }
  1340. describeInSuite(Op.in, 'IN', () => {
  1341. testSql(
  1342. { intAttr1: { [Op.in]: [] } },
  1343. {
  1344. default: '[intAttr1] IN (NULL)',
  1345. },
  1346. );
  1347. });
  1348. describeInSuite(Op.notIn, 'NOT IN', () => {
  1349. testSql(
  1350. { intAttr1: { [Op.notIn]: [] } },
  1351. {
  1352. default: '',
  1353. },
  1354. );
  1355. });
  1356. function describeLikeSuite(
  1357. operator: typeof Op.like | typeof Op.notLike | typeof Op.iLike | typeof Op.notILike,
  1358. sqlOperator: string,
  1359. ) {
  1360. // ensure like ops support the same typings, so we only have to test their typings once.
  1361. // unfortunately, at time of writing (TS 4.5.5), TypeScript
  1362. // does not detect an error in `{ [operator]: null }`
  1363. // but it does detect an error in { [Op.iLike]: null }`
  1364. expectTypeOf<WhereOperators[typeof Op.notLike]>().toEqualTypeOf<
  1365. WhereOperators[typeof Op.like]
  1366. >();
  1367. expectTypeOf<WhereOperators[typeof Op.iLike]>().toEqualTypeOf<
  1368. WhereOperators[typeof Op.like]
  1369. >();
  1370. expectTypeOf<WhereOperators[typeof Op.notILike]>().toEqualTypeOf<
  1371. WhereOperators[typeof Op.like]
  1372. >();
  1373. describe(`Op.${operator.description}`, () => {
  1374. expectTypeOf({ stringAttr: { [Op.like]: '%id' } }).toMatchTypeOf<TestModelWhere>();
  1375. testSql(
  1376. { stringAttr: { [operator]: '%id' } },
  1377. {
  1378. default: `[stringAttr] ${sqlOperator} '%id'`,
  1379. mssql: `[stringAttr] ${sqlOperator} N'%id'`,
  1380. },
  1381. );
  1382. // This test checks that the right data type is used to stringify the right operand
  1383. testSql(
  1384. { 'intAttr1::text': { [operator]: '%id' } },
  1385. {
  1386. default: `CAST([intAttr1] AS TEXT) ${sqlOperator} '%id'`,
  1387. mssql: `CAST([intAttr1] AS TEXT) ${sqlOperator} N'%id'`,
  1388. },
  1389. );
  1390. testSequelizeValueMethods(operator, sqlOperator);
  1391. testSupportsAnyAll(operator, sqlOperator, ['a', 'b', 'c'], 'stringAttr');
  1392. });
  1393. }
  1394. describeLikeSuite(Op.like, 'LIKE');
  1395. describeLikeSuite(Op.notLike, 'NOT LIKE');
  1396. describeLikeSuite(Op.iLike, 'ILIKE');
  1397. describeLikeSuite(Op.notILike, 'NOT ILIKE');
  1398. function describeOverlapSuite(
  1399. operator: typeof Op.overlap | typeof Op.contains | typeof Op.contained,
  1400. sqlOperator: string,
  1401. ) {
  1402. expectTypeOf<WhereOperators[typeof Op.contains]>().toEqualTypeOf<
  1403. WhereOperators[typeof Op.overlap]
  1404. >();
  1405. expectTypeOf<WhereOperators[typeof Op.contained]>().toEqualTypeOf<
  1406. WhereOperators[typeof Op.overlap]
  1407. >();
  1408. if (dialectSupportsArray()) {
  1409. describe(`Op.${operator.description} on ARRAY`, () => {
  1410. {
  1411. const ignoreRight: TestModelWhere = { intArrayAttr: { [Op.overlap]: [1, 2, 3] } };
  1412. testSql(
  1413. { intArrayAttr: { [operator]: [1, 2, 3] } },
  1414. {
  1415. default: `[intArrayAttr] ${sqlOperator} ARRAY[1,2,3]`,
  1416. },
  1417. );
  1418. }
  1419. testSequelizeValueMethods(operator, sqlOperator);
  1420. // ARRAY Overlap ARRAY doesn't support ANY or ALL, except with VALUES
  1421. // testSupportsAnyAll(operator, sqlOperator, [[1, 2], [1, 2]]);
  1422. {
  1423. const ignore: TestModelWhere = {
  1424. // @ts-expect-error -- cannot compare an array with a range!
  1425. intArrayAttr: { [Op.overlap]: [1, { value: 2, inclusive: true }] },
  1426. };
  1427. testSql(
  1428. { intArrayAttr: { [operator]: [1, { value: 2, inclusive: true }] } },
  1429. {
  1430. default: new Error('{ value: 2, inclusive: true } is not a valid integer'),
  1431. },
  1432. );
  1433. }
  1434. {
  1435. // @ts-expect-error -- not supported, testing that it throws
  1436. const ignoreWrong: TestModelWhere = { intArrayAttr: { [Op.overlap]: [col('col')] } };
  1437. testSql(
  1438. { intArrayAttr: { [operator]: [col('col')] } },
  1439. {
  1440. default: new Error(`Col { identifiers: [ 'col' ] } is not a valid integer`),
  1441. },
  1442. );
  1443. }
  1444. {
  1445. const ignoreWrong: TestModelWhere = {
  1446. // @ts-expect-error -- not supported, testing that it throws
  1447. intArrayAttr: { [Op.overlap]: [{ [Op.col]: 'col' }] },
  1448. };
  1449. testSql(
  1450. { intArrayAttr: { [operator]: [{ [Op.col]: 'col' }] } },
  1451. {
  1452. default: new Error(`{ [Symbol(col)]: 'col' } is not a valid integer`),
  1453. },
  1454. );
  1455. }
  1456. {
  1457. // @ts-expect-error -- not supported, testing that it throws
  1458. const ignoreWrong: TestModelWhere = {
  1459. intArrayAttr: { [Op.overlap]: [literal('literal')] },
  1460. };
  1461. testSql(
  1462. { intArrayAttr: { [operator]: [literal('literal')] } },
  1463. {
  1464. default: new Error(`Literal { val: [ 'literal' ] } is not a valid integer`),
  1465. },
  1466. );
  1467. }
  1468. });
  1469. }
  1470. if (dialectSupportsRange()) {
  1471. describe(`Op.${operator.description} on RANGE`, () => {
  1472. {
  1473. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2] } };
  1474. testSql(
  1475. { intRangeAttr: { [operator]: [1, 2] } },
  1476. {
  1477. default: `[intRangeAttr] ${sqlOperator} '[1,2)'::int4range`,
  1478. },
  1479. );
  1480. }
  1481. {
  1482. const ignoreRight: TestModelWhere = {
  1483. intRangeAttr: { [Op.overlap]: [1, { value: 2, inclusive: true }] },
  1484. };
  1485. testSql(
  1486. { intRangeAttr: { [operator]: [1, { value: 2, inclusive: true }] } },
  1487. {
  1488. // used 'postgres' because otherwise range is transformed to "1,2"
  1489. postgres: `"intRangeAttr" ${sqlOperator} '[1,2]'::int4range`,
  1490. },
  1491. );
  1492. }
  1493. {
  1494. const ignoreRight: TestModelWhere = {
  1495. intRangeAttr: { [Op.overlap]: [{ value: 1, inclusive: false }, 2] },
  1496. };
  1497. testSql(
  1498. { intRangeAttr: { [operator]: [{ value: 1, inclusive: false }, 2] } },
  1499. {
  1500. default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`,
  1501. },
  1502. );
  1503. }
  1504. {
  1505. const ignoreRight: TestModelWhere = {
  1506. intRangeAttr: {
  1507. [Op.overlap]: [
  1508. { value: 1, inclusive: false },
  1509. { value: 2, inclusive: false },
  1510. ],
  1511. },
  1512. };
  1513. testSql(
  1514. {
  1515. intRangeAttr: {
  1516. [operator]: [
  1517. { value: 1, inclusive: false },
  1518. { value: 2, inclusive: false },
  1519. ],
  1520. },
  1521. },
  1522. {
  1523. default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`,
  1524. },
  1525. );
  1526. }
  1527. {
  1528. // unbounded range (right)
  1529. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [10, null] } };
  1530. testSql(
  1531. {
  1532. intRangeAttr: { [operator]: [10, null] },
  1533. },
  1534. {
  1535. postgres: `"intRangeAttr" ${sqlOperator} '[10,)'::int4range`,
  1536. },
  1537. );
  1538. }
  1539. {
  1540. // unbounded range (left)
  1541. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [null, 10] } };
  1542. testSql(
  1543. {
  1544. intRangeAttr: { [operator]: [null, 10] },
  1545. },
  1546. {
  1547. postgres: `"intRangeAttr" ${sqlOperator} '[,10)'::int4range`,
  1548. },
  1549. );
  1550. }
  1551. {
  1552. // unbounded range (left)
  1553. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.overlap]: [null, null] } };
  1554. testSql(
  1555. {
  1556. intRangeAttr: { [operator]: [null, null] },
  1557. },
  1558. {
  1559. postgres: `"intRangeAttr" ${sqlOperator} '[,)'::int4range`,
  1560. },
  1561. );
  1562. }
  1563. {
  1564. const ignoreRight: TestModelWhere = {
  1565. dateRangeAttr: { [Op.overlap]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY] },
  1566. };
  1567. testSql(
  1568. {
  1569. dateRangeAttr: {
  1570. [operator]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY],
  1571. },
  1572. },
  1573. {
  1574. postgres: `"dateRangeAttr" ${sqlOperator} '[-infinity,infinity)'::tstzrange`,
  1575. },
  1576. );
  1577. }
  1578. {
  1579. // empty range
  1580. const ignoreRight: TestModelWhere = { dateRangeAttr: { [Op.overlap]: [] } };
  1581. testSql(
  1582. {
  1583. dateRangeAttr: { [operator]: [] },
  1584. },
  1585. {
  1586. postgres: `"dateRangeAttr" ${sqlOperator} 'empty'::tstzrange`,
  1587. },
  1588. );
  1589. }
  1590. {
  1591. // @ts-expect-error -- 'intRangeAttr' is a range, but right-hand side is a regular Array
  1592. const ignore: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2, 3] } };
  1593. testSql(
  1594. { intRangeAttr: { [operator]: [1, 2, 3] } },
  1595. {
  1596. default: new Error(
  1597. 'A range must either be an array with two elements, or an empty array for the empty range. Got [ 1, 2, 3 ].',
  1598. ),
  1599. },
  1600. );
  1601. }
  1602. testSequelizeValueMethods(operator, sqlOperator);
  1603. testSupportsAnyAll(operator, sqlOperator, [1, 2]);
  1604. });
  1605. }
  1606. }
  1607. describeOverlapSuite(Op.overlap, '&&');
  1608. describeOverlapSuite(Op.contains, '@>');
  1609. if (dialectSupportsRange()) {
  1610. describe('RANGE Op.contains ELEMENT', () => {
  1611. testSql(
  1612. {
  1613. intRangeAttr: { [Op.contains]: 1 },
  1614. },
  1615. {
  1616. postgres: `"intRangeAttr" @> 1`,
  1617. },
  1618. );
  1619. testSql(
  1620. // @ts-expect-error -- `ARRAY Op.contains ELEMENT` is not a valid query
  1621. { intArrayAttr: { [Op.contains]: 1 } },
  1622. {
  1623. default: new Error('1 is not a valid array'),
  1624. },
  1625. );
  1626. });
  1627. }
  1628. describeOverlapSuite(Op.contained, '<@');
  1629. describe('ELEMENT Op.contained RANGE', () => {
  1630. if (!dialectSupportsRange()) {
  1631. return;
  1632. }
  1633. testSql(
  1634. {
  1635. intAttr1: { [Op.contained]: [1, 2] },
  1636. },
  1637. {
  1638. postgres: `"intAttr1" <@ '[1,2)'::int4range`,
  1639. },
  1640. );
  1641. testSql(
  1642. {
  1643. bigIntAttr: { [Op.contained]: [1, 2] },
  1644. },
  1645. {
  1646. postgres: `"bigIntAttr" <@ '[1,2)'::int8range`,
  1647. },
  1648. );
  1649. testSql(
  1650. {
  1651. dateAttr: {
  1652. [Op.contained]: [new Date('2020-01-01T00:00:00Z'), new Date('2021-01-01T00:00:00Z')],
  1653. },
  1654. },
  1655. {
  1656. postgres: `"dateAttr" <@ '[2020-01-01 00:00:00.000 +00:00,2021-01-01 00:00:00.000 +00:00)'::tstzrange`,
  1657. },
  1658. );
  1659. /*
  1660. TODO:
  1661. numrange — Range of numeric
  1662. tsrange — Range of timestamp without time zone
  1663. daterange — Range of date
  1664. */
  1665. });
  1666. describe('Op.startsWith', () => {
  1667. // TODO: use implementation not based on "LIKE"
  1668. // mysql, mariadb: locate()
  1669. // postgres:, ^@
  1670. // snowflake, ibmi, db2: position()
  1671. // mssql: CHARINDEX()
  1672. // sqlite3: INSTR()
  1673. testSql(
  1674. {
  1675. stringAttr: {
  1676. [Op.startsWith]: 'swagger',
  1677. },
  1678. },
  1679. {
  1680. default: `[stringAttr] LIKE 'swagger%'`,
  1681. mssql: `[stringAttr] LIKE N'swagger%'`,
  1682. },
  1683. );
  1684. testSql(
  1685. {
  1686. stringAttr: {
  1687. [Op.startsWith]: "sql'injection",
  1688. },
  1689. },
  1690. {
  1691. default: `[stringAttr] LIKE 'sql''injection%'`,
  1692. mysql: `\`stringAttr\` LIKE 'sql\\'injection%'`,
  1693. mariadb: `\`stringAttr\` LIKE 'sql\\'injection%'`,
  1694. mssql: `[stringAttr] LIKE N'sql''injection%'`,
  1695. },
  1696. );
  1697. // startsWith should escape anything that has special meaning in LIKE
  1698. testSql.skip(
  1699. {
  1700. stringAttr: {
  1701. [Op.startsWith]: 'like%injection',
  1702. },
  1703. },
  1704. {
  1705. default: String.raw`[stringAttr] LIKE 'sql\%injection%' ESCAPE '\'`,
  1706. mssql: String.raw`[stringAttr] LIKE N'sql\%injection%' ESCAPE '\'`,
  1707. },
  1708. );
  1709. testSql(
  1710. {
  1711. stringAttr: {
  1712. [Op.startsWith]: literal('$bind'),
  1713. },
  1714. },
  1715. {
  1716. default: `[stringAttr] LIKE CONCAT($bind, '%')`,
  1717. mssql: `[stringAttr] LIKE CONCAT($bind, N'%')`,
  1718. },
  1719. );
  1720. testSql(
  1721. {
  1722. stringAttr: {
  1723. [Op.startsWith]: col('username'),
  1724. },
  1725. },
  1726. {
  1727. default: `[stringAttr] LIKE CONCAT([username], '%')`,
  1728. mssql: `[stringAttr] LIKE CONCAT([username], N'%')`,
  1729. },
  1730. );
  1731. testSql(
  1732. {
  1733. stringAttr: {
  1734. [Op.startsWith]: { [Op.col]: 'username' },
  1735. },
  1736. },
  1737. {
  1738. default: `[stringAttr] LIKE CONCAT([username], '%')`,
  1739. mssql: `[stringAttr] LIKE CONCAT([username], N'%')`,
  1740. },
  1741. );
  1742. testSql(
  1743. {
  1744. stringAttr: {
  1745. [Op.startsWith]: fn('NOW'),
  1746. },
  1747. },
  1748. {
  1749. default: `[stringAttr] LIKE CONCAT(NOW(), '%')`,
  1750. mssql: `[stringAttr] LIKE CONCAT(NOW(), N'%')`,
  1751. },
  1752. );
  1753. testSql(
  1754. {
  1755. stringAttr: {
  1756. [Op.startsWith]: cast(fn('NOW'), 'string'),
  1757. },
  1758. },
  1759. {
  1760. default: `[stringAttr] LIKE CONCAT(CAST(NOW() AS STRING), '%')`,
  1761. mssql: `[stringAttr] LIKE CONCAT(CAST(NOW() AS STRING), N'%')`,
  1762. },
  1763. );
  1764. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPe is '\')
  1765. testSql(
  1766. // @ts-expect-error -- startsWith is not compatible with Op.any
  1767. { stringAttr: { [Op.startsWith]: { [Op.any]: ['test'] } } },
  1768. {
  1769. default: new Error(
  1770. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  1771. ),
  1772. },
  1773. );
  1774. testSql(
  1775. // @ts-expect-error -- startsWith is not compatible with Op.all
  1776. { stringAttr: { [Op.startsWith]: { [Op.all]: ['test'] } } },
  1777. {
  1778. default: new Error(
  1779. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  1780. ),
  1781. },
  1782. );
  1783. });
  1784. describe('Op.endsWith', () => {
  1785. testSql(
  1786. {
  1787. stringAttr: {
  1788. [Op.endsWith]: 'swagger',
  1789. },
  1790. },
  1791. {
  1792. default: `[stringAttr] LIKE '%swagger'`,
  1793. mssql: `[stringAttr] LIKE N'%swagger'`,
  1794. },
  1795. );
  1796. testSql(
  1797. {
  1798. stringAttr: {
  1799. [Op.endsWith]: "sql'injection",
  1800. },
  1801. },
  1802. {
  1803. default: `[stringAttr] LIKE '%sql''injection'`,
  1804. mysql: `\`stringAttr\` LIKE '%sql\\'injection'`,
  1805. mariadb: `\`stringAttr\` LIKE '%sql\\'injection'`,
  1806. mssql: `[stringAttr] LIKE N'%sql''injection'`,
  1807. },
  1808. );
  1809. // endsWith should escape anything that has special meaning in LIKE
  1810. testSql.skip(
  1811. {
  1812. stringAttr: {
  1813. [Op.endsWith]: 'like%injection',
  1814. },
  1815. },
  1816. {
  1817. default: String.raw`[stringAttr] LIKE '%sql\%injection' ESCAPE '\'`,
  1818. mssql: String.raw`[stringAttr] LIKE N'%sql\%injection' ESCAPE '\'`,
  1819. },
  1820. );
  1821. testSql(
  1822. {
  1823. stringAttr: {
  1824. [Op.endsWith]: literal('$bind'),
  1825. },
  1826. },
  1827. {
  1828. default: `[stringAttr] LIKE CONCAT('%', $bind)`,
  1829. mssql: `[stringAttr] LIKE CONCAT(N'%', $bind)`,
  1830. },
  1831. );
  1832. testSql(
  1833. {
  1834. stringAttr: {
  1835. [Op.endsWith]: col('username'),
  1836. },
  1837. },
  1838. {
  1839. default: `[stringAttr] LIKE CONCAT('%', [username])`,
  1840. mssql: `[stringAttr] LIKE CONCAT(N'%', [username])`,
  1841. },
  1842. );
  1843. testSql(
  1844. {
  1845. stringAttr: {
  1846. [Op.endsWith]: { [Op.col]: 'username' },
  1847. },
  1848. },
  1849. {
  1850. default: `[stringAttr] LIKE CONCAT('%', [username])`,
  1851. mssql: `[stringAttr] LIKE CONCAT(N'%', [username])`,
  1852. },
  1853. );
  1854. testSql(
  1855. {
  1856. stringAttr: {
  1857. [Op.endsWith]: fn('NOW'),
  1858. },
  1859. },
  1860. {
  1861. default: `[stringAttr] LIKE CONCAT('%', NOW())`,
  1862. mssql: `[stringAttr] LIKE CONCAT(N'%', NOW())`,
  1863. },
  1864. );
  1865. testSql(
  1866. {
  1867. stringAttr: {
  1868. [Op.endsWith]: cast(fn('NOW'), 'string'),
  1869. },
  1870. },
  1871. {
  1872. default: `[stringAttr] LIKE CONCAT('%', CAST(NOW() AS STRING))`,
  1873. mssql: `[stringAttr] LIKE CONCAT(N'%', CAST(NOW() AS STRING))`,
  1874. },
  1875. );
  1876. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\')
  1877. testSql(
  1878. // @ts-expect-error -- startsWith is not compatible with Op.any
  1879. { stringAttr: { [Op.endsWith]: { [Op.any]: ['test'] } } },
  1880. {
  1881. default: new Error(
  1882. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  1883. ),
  1884. },
  1885. );
  1886. testSql(
  1887. // @ts-expect-error -- startsWith is not compatible with Op.all
  1888. { stringAttr: { [Op.endsWith]: { [Op.all]: ['test'] } } },
  1889. {
  1890. default: new Error(
  1891. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  1892. ),
  1893. },
  1894. );
  1895. });
  1896. describe('Op.substring', () => {
  1897. // TODO: use implementation not based on "LIKE"
  1898. // mysql, mariadb: locate()
  1899. // postgres:, position()
  1900. // snowflake, ibmi, db2: position()
  1901. // mssql: CHARINDEX()
  1902. // sqlite3: INSTR()
  1903. testSql(
  1904. {
  1905. stringAttr: {
  1906. [Op.substring]: 'swagger',
  1907. },
  1908. },
  1909. {
  1910. default: `[stringAttr] LIKE '%swagger%'`,
  1911. mssql: `[stringAttr] LIKE N'%swagger%'`,
  1912. },
  1913. );
  1914. testSql(
  1915. {
  1916. stringAttr: {
  1917. [Op.substring]: "sql'injection",
  1918. },
  1919. },
  1920. {
  1921. default: `[stringAttr] LIKE '%sql''injection%'`,
  1922. mysql: `\`stringAttr\` LIKE '%sql\\'injection%'`,
  1923. mariadb: `\`stringAttr\` LIKE '%sql\\'injection%'`,
  1924. mssql: `[stringAttr] LIKE N'%sql''injection%'`,
  1925. },
  1926. );
  1927. // substring should escape anything that has special meaning in LIKE
  1928. testSql.skip(
  1929. {
  1930. stringAttr: {
  1931. [Op.substring]: 'like%injection',
  1932. },
  1933. },
  1934. {
  1935. default: String.raw`[stringAttr] LIKE '%sql\%injection%' ESCAPE '\'`,
  1936. mssql: String.raw`[stringAttr] LIKE N'%sql\%injection%' ESCAPE '\'`,
  1937. },
  1938. );
  1939. testSql(
  1940. {
  1941. stringAttr: {
  1942. [Op.substring]: literal('$bind'),
  1943. },
  1944. },
  1945. {
  1946. default: `[stringAttr] LIKE CONCAT('%', $bind, '%')`,
  1947. mssql: `[stringAttr] LIKE CONCAT(N'%', $bind, N'%')`,
  1948. },
  1949. );
  1950. testSql(
  1951. {
  1952. stringAttr: {
  1953. [Op.substring]: col('username'),
  1954. },
  1955. },
  1956. {
  1957. default: `[stringAttr] LIKE CONCAT('%', [username], '%')`,
  1958. mssql: `[stringAttr] LIKE CONCAT(N'%', [username], N'%')`,
  1959. },
  1960. );
  1961. testSql(
  1962. {
  1963. stringAttr: {
  1964. [Op.substring]: { [Op.col]: 'username' },
  1965. },
  1966. },
  1967. {
  1968. default: `[stringAttr] LIKE CONCAT('%', [username], '%')`,
  1969. mssql: `[stringAttr] LIKE CONCAT(N'%', [username], N'%')`,
  1970. },
  1971. );
  1972. testSql(
  1973. {
  1974. stringAttr: {
  1975. [Op.substring]: fn('NOW'),
  1976. },
  1977. },
  1978. {
  1979. default: `[stringAttr] LIKE CONCAT('%', NOW(), '%')`,
  1980. mssql: `[stringAttr] LIKE CONCAT(N'%', NOW(), N'%')`,
  1981. },
  1982. );
  1983. testSql(
  1984. {
  1985. stringAttr: {
  1986. [Op.substring]: cast(fn('NOW'), 'string'),
  1987. },
  1988. },
  1989. {
  1990. default: `[stringAttr] LIKE CONCAT('%', CAST(NOW() AS STRING), '%')`,
  1991. mssql: `[stringAttr] LIKE CONCAT(N'%', CAST(NOW() AS STRING), N'%')`,
  1992. },
  1993. );
  1994. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\')
  1995. testSql(
  1996. // @ts-expect-error -- startsWith is not compatible with Op.any
  1997. { stringAttr: { [Op.substring]: { [Op.any]: ['test'] } } },
  1998. {
  1999. default: new Error(
  2000. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2001. ),
  2002. },
  2003. );
  2004. testSql(
  2005. // @ts-expect-error -- startsWith is not compatible with Op.all
  2006. { stringAttr: { [Op.substring]: { [Op.all]: ['test'] } } },
  2007. {
  2008. default: new Error(
  2009. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2010. ),
  2011. },
  2012. );
  2013. });
  2014. describe('Op.notStartsWith', () => {
  2015. testSql(
  2016. {
  2017. stringAttr: {
  2018. [Op.notStartsWith]: 'swagger',
  2019. },
  2020. },
  2021. {
  2022. default: `[stringAttr] NOT LIKE 'swagger%'`,
  2023. mssql: `[stringAttr] NOT LIKE N'swagger%'`,
  2024. },
  2025. );
  2026. testSql(
  2027. {
  2028. stringAttr: {
  2029. [Op.notStartsWith]: "sql'injection",
  2030. },
  2031. },
  2032. {
  2033. default: `[stringAttr] NOT LIKE 'sql''injection%'`,
  2034. mysql: `\`stringAttr\` NOT LIKE 'sql\\'injection%'`,
  2035. mariadb: `\`stringAttr\` NOT LIKE 'sql\\'injection%'`,
  2036. mssql: `[stringAttr] NOT LIKE N'sql''injection%'`,
  2037. },
  2038. );
  2039. // startsWith should escape anything that has special meaning in LIKE
  2040. testSql.skip(
  2041. {
  2042. stringAttr: {
  2043. [Op.notStartsWith]: 'like%injection',
  2044. },
  2045. },
  2046. {
  2047. default: String.raw`[stringAttr] NOT LIKE 'sql\%injection%' ESCAPE '\'`,
  2048. mssql: String.raw`[stringAttr] NOT LIKE N'sql\%injection%' ESCAPE '\'`,
  2049. },
  2050. );
  2051. testSql(
  2052. {
  2053. stringAttr: {
  2054. [Op.notStartsWith]: literal('$bind'),
  2055. },
  2056. },
  2057. {
  2058. default: `[stringAttr] NOT LIKE CONCAT($bind, '%')`,
  2059. mssql: `[stringAttr] NOT LIKE CONCAT($bind, N'%')`,
  2060. },
  2061. );
  2062. testSql(
  2063. {
  2064. stringAttr: {
  2065. [Op.notStartsWith]: col('username'),
  2066. },
  2067. },
  2068. {
  2069. default: `[stringAttr] NOT LIKE CONCAT([username], '%')`,
  2070. mssql: `[stringAttr] NOT LIKE CONCAT([username], N'%')`,
  2071. },
  2072. );
  2073. testSql(
  2074. {
  2075. stringAttr: {
  2076. [Op.notStartsWith]: { [Op.col]: 'username' },
  2077. },
  2078. },
  2079. {
  2080. default: `[stringAttr] NOT LIKE CONCAT([username], '%')`,
  2081. mssql: `[stringAttr] NOT LIKE CONCAT([username], N'%')`,
  2082. },
  2083. );
  2084. testSql(
  2085. {
  2086. stringAttr: {
  2087. [Op.notStartsWith]: fn('NOW'),
  2088. },
  2089. },
  2090. {
  2091. default: `[stringAttr] NOT LIKE CONCAT(NOW(), '%')`,
  2092. mssql: `[stringAttr] NOT LIKE CONCAT(NOW(), N'%')`,
  2093. },
  2094. );
  2095. testSql(
  2096. {
  2097. stringAttr: {
  2098. [Op.notStartsWith]: cast(fn('NOW'), 'string'),
  2099. },
  2100. },
  2101. {
  2102. default: `[stringAttr] NOT LIKE CONCAT(CAST(NOW() AS STRING), '%')`,
  2103. mssql: `[stringAttr] NOT LIKE CONCAT(CAST(NOW() AS STRING), N'%')`,
  2104. },
  2105. );
  2106. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPe is '\')
  2107. testSql(
  2108. // @ts-expect-error -- notStartsWith is not compatible with Op.any
  2109. { stringAttr: { [Op.notStartsWith]: { [Op.any]: ['test'] } } },
  2110. {
  2111. default: new Error(
  2112. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2113. ),
  2114. },
  2115. );
  2116. testSql(
  2117. // @ts-expect-error -- notStartsWith is not compatible with Op.all
  2118. { stringAttr: { [Op.notStartsWith]: { [Op.all]: ['test'] } } },
  2119. {
  2120. default: new Error(
  2121. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2122. ),
  2123. },
  2124. );
  2125. });
  2126. describe('Op.notEndsWith', () => {
  2127. testSql(
  2128. {
  2129. stringAttr: {
  2130. [Op.notEndsWith]: 'swagger',
  2131. },
  2132. },
  2133. {
  2134. default: `[stringAttr] NOT LIKE '%swagger'`,
  2135. mssql: `[stringAttr] NOT LIKE N'%swagger'`,
  2136. },
  2137. );
  2138. testSql(
  2139. {
  2140. stringAttr: {
  2141. [Op.notEndsWith]: "sql'injection",
  2142. },
  2143. },
  2144. {
  2145. default: `[stringAttr] NOT LIKE '%sql''injection'`,
  2146. mysql: `\`stringAttr\` NOT LIKE '%sql\\'injection'`,
  2147. mariadb: `\`stringAttr\` NOT LIKE '%sql\\'injection'`,
  2148. mssql: `[stringAttr] NOT LIKE N'%sql''injection'`,
  2149. },
  2150. );
  2151. // notEndsWith should escape anything that has special meaning in LIKE
  2152. testSql.skip(
  2153. {
  2154. stringAttr: {
  2155. [Op.notEndsWith]: 'like%injection',
  2156. },
  2157. },
  2158. {
  2159. default: String.raw`[stringAttr] NOT LIKE '%sql\%injection' ESCAPE '\'`,
  2160. mssql: String.raw`[stringAttr] NOT LIKE N'%sql\%injection' ESCAPE '\'`,
  2161. },
  2162. );
  2163. testSql(
  2164. {
  2165. stringAttr: {
  2166. [Op.notEndsWith]: literal('$bind'),
  2167. },
  2168. },
  2169. {
  2170. default: `[stringAttr] NOT LIKE CONCAT('%', $bind)`,
  2171. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', $bind)`,
  2172. },
  2173. );
  2174. testSql(
  2175. {
  2176. stringAttr: {
  2177. [Op.notEndsWith]: col('username'),
  2178. },
  2179. },
  2180. {
  2181. default: `[stringAttr] NOT LIKE CONCAT('%', [username])`,
  2182. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username])`,
  2183. },
  2184. );
  2185. testSql(
  2186. {
  2187. stringAttr: {
  2188. [Op.notEndsWith]: { [Op.col]: 'username' },
  2189. },
  2190. },
  2191. {
  2192. default: `[stringAttr] NOT LIKE CONCAT('%', [username])`,
  2193. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username])`,
  2194. },
  2195. );
  2196. testSql(
  2197. {
  2198. stringAttr: {
  2199. [Op.notEndsWith]: fn('NOW'),
  2200. },
  2201. },
  2202. {
  2203. default: `[stringAttr] NOT LIKE CONCAT('%', NOW())`,
  2204. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', NOW())`,
  2205. },
  2206. );
  2207. testSql(
  2208. {
  2209. stringAttr: {
  2210. [Op.notEndsWith]: cast(fn('NOW'), 'string'),
  2211. },
  2212. },
  2213. {
  2214. default: `[stringAttr] NOT LIKE CONCAT('%', CAST(NOW() AS STRING))`,
  2215. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', CAST(NOW() AS STRING))`,
  2216. },
  2217. );
  2218. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\')
  2219. testSql(
  2220. // @ts-expect-error -- notEndsWith is not compatible with Op.any
  2221. { stringAttr: { [Op.notEndsWith]: { [Op.any]: ['test'] } } },
  2222. {
  2223. default: new Error(
  2224. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2225. ),
  2226. },
  2227. );
  2228. testSql(
  2229. // @ts-expect-error -- notEndsWith is not compatible with Op.all
  2230. { stringAttr: { [Op.notEndsWith]: { [Op.all]: ['test'] } } },
  2231. {
  2232. default: new Error(
  2233. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2234. ),
  2235. },
  2236. );
  2237. });
  2238. describe('Op.notSubstring', () => {
  2239. testSql(
  2240. {
  2241. stringAttr: {
  2242. [Op.notSubstring]: 'swagger',
  2243. },
  2244. },
  2245. {
  2246. default: `[stringAttr] NOT LIKE '%swagger%'`,
  2247. mssql: `[stringAttr] NOT LIKE N'%swagger%'`,
  2248. },
  2249. );
  2250. testSql(
  2251. {
  2252. stringAttr: {
  2253. [Op.notSubstring]: "sql'injection",
  2254. },
  2255. },
  2256. {
  2257. default: `[stringAttr] NOT LIKE '%sql''injection%'`,
  2258. mysql: `\`stringAttr\` NOT LIKE '%sql\\'injection%'`,
  2259. mariadb: `\`stringAttr\` NOT LIKE '%sql\\'injection%'`,
  2260. mssql: `[stringAttr] NOT LIKE N'%sql''injection%'`,
  2261. },
  2262. );
  2263. // notSubstring should escape anything that has special meaning in LIKE
  2264. testSql.skip(
  2265. {
  2266. stringAttr: {
  2267. [Op.notSubstring]: 'like%injection',
  2268. },
  2269. },
  2270. {
  2271. default: String.raw`[stringAttr] NOT LIKE '%sql\%injection%' ESCAPE '\'`,
  2272. mssql: String.raw`[stringAttr] NOT LIKE N'%sql\%injection%' ESCAPE '\'`,
  2273. },
  2274. );
  2275. testSql(
  2276. {
  2277. stringAttr: {
  2278. [Op.notSubstring]: literal('$bind'),
  2279. },
  2280. },
  2281. {
  2282. default: `[stringAttr] NOT LIKE CONCAT('%', $bind, '%')`,
  2283. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', $bind, N'%')`,
  2284. },
  2285. );
  2286. testSql(
  2287. {
  2288. stringAttr: {
  2289. [Op.notSubstring]: col('username'),
  2290. },
  2291. },
  2292. {
  2293. default: `[stringAttr] NOT LIKE CONCAT('%', [username], '%')`,
  2294. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username], N'%')`,
  2295. },
  2296. );
  2297. testSql(
  2298. {
  2299. stringAttr: {
  2300. [Op.notSubstring]: { [Op.col]: 'username' },
  2301. },
  2302. },
  2303. {
  2304. default: `[stringAttr] NOT LIKE CONCAT('%', [username], '%')`,
  2305. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', [username], N'%')`,
  2306. },
  2307. );
  2308. testSql(
  2309. {
  2310. stringAttr: {
  2311. [Op.notSubstring]: fn('NOW'),
  2312. },
  2313. },
  2314. {
  2315. default: `[stringAttr] NOT LIKE CONCAT('%', NOW(), '%')`,
  2316. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', NOW(), N'%')`,
  2317. },
  2318. );
  2319. testSql(
  2320. {
  2321. stringAttr: {
  2322. [Op.notSubstring]: cast(fn('NOW'), 'string'),
  2323. },
  2324. },
  2325. {
  2326. default: `[stringAttr] NOT LIKE CONCAT('%', CAST(NOW() AS STRING), '%')`,
  2327. mssql: `[stringAttr] NOT LIKE CONCAT(N'%', CAST(NOW() AS STRING), N'%')`,
  2328. },
  2329. );
  2330. // these cannot be compatible because it's not possible to provide a ESCAPE clause (although the default ESCAPE is '\')
  2331. testSql(
  2332. // @ts-expect-error -- notSubstring is not compatible with Op.any
  2333. { stringAttr: { [Op.notSubstring]: { [Op.any]: ['test'] } } },
  2334. {
  2335. default: new Error(
  2336. `{ [Symbol(any)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2337. ),
  2338. },
  2339. );
  2340. testSql(
  2341. // @ts-expect-error -- notSubstring is not compatible with Op.all
  2342. { stringAttr: { [Op.notSubstring]: { [Op.all]: ['test'] } } },
  2343. {
  2344. default: new Error(
  2345. `{ [Symbol(all)]: [ 'test' ] } is not a valid string. Only the string type is accepted for non-binary strings.`,
  2346. ),
  2347. },
  2348. );
  2349. });
  2350. function describeRegexpSuite(
  2351. operator: typeof Op.regexp | typeof Op.iRegexp | typeof Op.notRegexp | typeof Op.notIRegexp,
  2352. sqlOperator: string,
  2353. ) {
  2354. expectTypeOf<WhereOperators[typeof Op.iRegexp]>().toEqualTypeOf<
  2355. WhereOperators[typeof Op.regexp]
  2356. >();
  2357. expectTypeOf<WhereOperators[typeof Op.notRegexp]>().toEqualTypeOf<
  2358. WhereOperators[typeof Op.regexp]
  2359. >();
  2360. expectTypeOf<WhereOperators[typeof Op.notIRegexp]>().toEqualTypeOf<
  2361. WhereOperators[typeof Op.regexp]
  2362. >();
  2363. describe(`Op.${operator.description}`, () => {
  2364. {
  2365. const ignore: TestModelWhere = { stringAttr: { [Op.regexp]: '^sw.*r$' } };
  2366. }
  2367. testSql(
  2368. { stringAttr: { [operator]: '^sw.*r$' } },
  2369. {
  2370. default: `[stringAttr] ${sqlOperator} '^sw.*r$'`,
  2371. },
  2372. );
  2373. testSql(
  2374. { stringAttr: { [operator]: '^new\nline$' } },
  2375. {
  2376. default: `[stringAttr] ${sqlOperator} '^new\nline$'`,
  2377. mariadb: `\`stringAttr\` ${sqlOperator} '^new\\nline$'`,
  2378. mysql: `\`stringAttr\` ${sqlOperator} '^new\\nline$'`,
  2379. },
  2380. );
  2381. testSequelizeValueMethods(operator, sqlOperator);
  2382. testSupportsAnyAll(operator, sqlOperator, ['^a$', '^b$'], 'stringAttr');
  2383. });
  2384. }
  2385. if (sequelize.dialect.supports.REGEXP) {
  2386. describeRegexpSuite(Op.regexp, sequelize.dialect.name === 'postgres' ? '~' : 'REGEXP');
  2387. describeRegexpSuite(
  2388. Op.notRegexp,
  2389. sequelize.dialect.name === 'postgres' ? '!~' : 'NOT REGEXP',
  2390. );
  2391. }
  2392. if (sequelize.dialect.supports.IREGEXP) {
  2393. describeRegexpSuite(Op.iRegexp, '~*');
  2394. describeRegexpSuite(Op.notIRegexp, '!~*');
  2395. }
  2396. if (sequelize.dialect.supports.dataTypes.TSVECTOR) {
  2397. describe('Op.match', () => {
  2398. testSql(
  2399. { stringAttr: { [Op.match]: fn('to_tsvector', 'swagger') } },
  2400. {
  2401. default: `[stringAttr] @@ to_tsvector('swagger')`,
  2402. },
  2403. );
  2404. testSequelizeValueMethods(Op.match, '@@');
  2405. // TODO
  2406. // testSupportsAnyAll(Op.match, '@@', [fn('to_tsvector', 'a'), fn('to_tsvector', 'b')]);
  2407. });
  2408. }
  2409. function describeAdjacentRangeSuite(
  2410. operator:
  2411. | typeof Op.adjacent
  2412. | typeof Op.strictLeft
  2413. | typeof Op.strictRight
  2414. | typeof Op.noExtendLeft
  2415. | typeof Op.noExtendRight,
  2416. sqlOperator: string,
  2417. ) {
  2418. if (!dialectSupportsRange()) {
  2419. return;
  2420. }
  2421. expectTypeOf<WhereOperators[typeof Op.strictLeft]>().toEqualTypeOf<
  2422. WhereOperators[typeof Op.adjacent]
  2423. >();
  2424. expectTypeOf<WhereOperators[typeof Op.strictRight]>().toEqualTypeOf<
  2425. WhereOperators[typeof Op.adjacent]
  2426. >();
  2427. expectTypeOf<WhereOperators[typeof Op.noExtendLeft]>().toEqualTypeOf<
  2428. WhereOperators[typeof Op.adjacent]
  2429. >();
  2430. expectTypeOf<WhereOperators[typeof Op.noExtendRight]>().toEqualTypeOf<
  2431. WhereOperators[typeof Op.adjacent]
  2432. >();
  2433. describe(`RANGE Op.${operator.description} RANGE`, () => {
  2434. {
  2435. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [1, 2] } };
  2436. testSql(
  2437. { intRangeAttr: { [operator]: [1, 2] } },
  2438. {
  2439. default: `[intRangeAttr] ${sqlOperator} '[1,2)'::int4range`,
  2440. },
  2441. );
  2442. }
  2443. {
  2444. const ignoreRight: TestModelWhere = {
  2445. intRangeAttr: { [Op.adjacent]: [1, { value: 2, inclusive: true }] },
  2446. };
  2447. testSql(
  2448. { intRangeAttr: { [operator]: [1, { value: 2, inclusive: true }] } },
  2449. {
  2450. // used 'postgres' because otherwise range is transformed to "1,2"
  2451. postgres: `"intRangeAttr" ${sqlOperator} '[1,2]'::int4range`,
  2452. },
  2453. );
  2454. }
  2455. {
  2456. const ignoreRight: TestModelWhere = {
  2457. intRangeAttr: { [Op.adjacent]: [{ value: 1, inclusive: false }, 2] },
  2458. };
  2459. testSql(
  2460. { intRangeAttr: { [operator]: [{ value: 1, inclusive: false }, 2] } },
  2461. {
  2462. default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`,
  2463. },
  2464. );
  2465. }
  2466. {
  2467. const ignoreRight: TestModelWhere = {
  2468. intRangeAttr: {
  2469. [Op.adjacent]: [
  2470. { value: 1, inclusive: false },
  2471. { value: 2, inclusive: false },
  2472. ],
  2473. },
  2474. };
  2475. testSql(
  2476. {
  2477. intRangeAttr: {
  2478. [operator]: [
  2479. { value: 1, inclusive: false },
  2480. { value: 2, inclusive: false },
  2481. ],
  2482. },
  2483. },
  2484. {
  2485. default: `[intRangeAttr] ${sqlOperator} '(1,2)'::int4range`,
  2486. },
  2487. );
  2488. }
  2489. {
  2490. // unbounded range (right)
  2491. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [10, null] } };
  2492. testSql(
  2493. {
  2494. intRangeAttr: { [operator]: [10, null] },
  2495. },
  2496. {
  2497. postgres: `"intRangeAttr" ${sqlOperator} '[10,)'::int4range`,
  2498. },
  2499. );
  2500. }
  2501. {
  2502. // unbounded range (left)
  2503. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [null, 10] } };
  2504. testSql(
  2505. {
  2506. intRangeAttr: { [operator]: [null, 10] },
  2507. },
  2508. {
  2509. postgres: `"intRangeAttr" ${sqlOperator} '[,10)'::int4range`,
  2510. },
  2511. );
  2512. }
  2513. {
  2514. // unbounded range (left)
  2515. const ignoreRight: TestModelWhere = { intRangeAttr: { [Op.adjacent]: [null, null] } };
  2516. testSql(
  2517. {
  2518. intRangeAttr: { [operator]: [null, null] },
  2519. },
  2520. {
  2521. postgres: `"intRangeAttr" ${sqlOperator} '[,)'::int4range`,
  2522. },
  2523. );
  2524. }
  2525. {
  2526. const ignoreRight: TestModelWhere = {
  2527. dateRangeAttr: { [Op.adjacent]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY] },
  2528. };
  2529. testSql(
  2530. {
  2531. dateRangeAttr: {
  2532. [operator]: [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY],
  2533. },
  2534. },
  2535. {
  2536. postgres: `"dateRangeAttr" ${sqlOperator} '[-infinity,infinity)'::tstzrange`,
  2537. },
  2538. );
  2539. }
  2540. {
  2541. // empty range
  2542. const ignoreRight: TestModelWhere = { dateRangeAttr: { [Op.adjacent]: [] } };
  2543. testSql(
  2544. {
  2545. dateRangeAttr: { [operator]: [] },
  2546. },
  2547. {
  2548. postgres: `"dateRangeAttr" ${sqlOperator} 'empty'::tstzrange`,
  2549. },
  2550. );
  2551. }
  2552. {
  2553. // @ts-expect-error -- 'intRangeAttr' is a range, but right-hand side is a regular Array
  2554. const ignore: TestModelWhere = { intRangeAttr: { [Op.overlap]: [1, 2, 3] } };
  2555. testSql(
  2556. { intRangeAttr: { [operator]: [1, 2, 3] } },
  2557. {
  2558. default: new Error(
  2559. 'A range must either be an array with two elements, or an empty array for the empty range. Got [ 1, 2, 3 ].',
  2560. ),
  2561. },
  2562. );
  2563. }
  2564. });
  2565. }
  2566. describeAdjacentRangeSuite(Op.adjacent, '-|-');
  2567. describeAdjacentRangeSuite(Op.strictLeft, '<<');
  2568. describeAdjacentRangeSuite(Op.strictRight, '>>');
  2569. describeAdjacentRangeSuite(Op.noExtendLeft, '&>');
  2570. describeAdjacentRangeSuite(Op.noExtendRight, '&<');
  2571. if (sequelize.dialect.supports.jsonOperations) {
  2572. describe('JSON Operations', () => {
  2573. {
  2574. // @ts-expect-error -- attribute 'doesNotExist' does not exist.
  2575. const ignore: TestModelWhere = { 'doesNotExist.nested': 'value' };
  2576. }
  2577. {
  2578. // @ts-expect-error -- attribute 'doesNotExist' does not exist.
  2579. const ignore: TestModelWhere = { '$doesNotExist$.nested': 'value' };
  2580. }
  2581. testSql(
  2582. { jsonAttr: 'value' },
  2583. {
  2584. default: `[jsonAttr] = '"value"'`,
  2585. mysql: `\`jsonAttr\` = CAST('"value"' AS JSON)`,
  2586. mssql: `[jsonAttr] = N'"value"'`,
  2587. },
  2588. );
  2589. testSql(
  2590. { jsonAttr: null },
  2591. {
  2592. default: new Error('You must be explicit'),
  2593. },
  2594. );
  2595. testSql(
  2596. { jsonAttr: { [Op.eq]: null } },
  2597. {
  2598. default: `[jsonAttr] = 'null'`,
  2599. mysql: `\`jsonAttr\` = CAST('null' AS JSON)`,
  2600. mssql: `[jsonAttr] = N'null'`,
  2601. },
  2602. );
  2603. testSql(
  2604. { jsonAttr: { [Op.is]: null } },
  2605. {
  2606. default: `[jsonAttr] IS NULL`,
  2607. },
  2608. );
  2609. testSql(
  2610. { jsonAttr: JSON_NULL },
  2611. {
  2612. default: `[jsonAttr] = 'null'`,
  2613. mysql: `\`jsonAttr\` = CAST('null' AS JSON)`,
  2614. mssql: `[jsonAttr] = N'null'`,
  2615. },
  2616. );
  2617. testSql(
  2618. { jsonAttr: SQL_NULL },
  2619. {
  2620. default: `[jsonAttr] IS NULL`,
  2621. },
  2622. );
  2623. if (dialectSupportsJsonQuotedExtraction()) {
  2624. testSql(
  2625. { 'jsonAttr.nested': 'value' },
  2626. {
  2627. postgres: `"jsonAttr"->'nested' = '"value"'`,
  2628. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`,
  2629. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`,
  2630. mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`,
  2631. },
  2632. );
  2633. testSql(
  2634. { 'jsonAttr.nested': null },
  2635. {
  2636. default: new Error('You must be explicit'),
  2637. },
  2638. );
  2639. testSql(
  2640. { 'jsonAttr.nested': JSON_NULL },
  2641. {
  2642. postgres: `"jsonAttr"->'nested' = 'null'`,
  2643. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = 'null'`,
  2644. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = 'null'`,
  2645. mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('null' AS JSON)`,
  2646. },
  2647. );
  2648. testSql(
  2649. { 'jsonAttr.nested': SQL_NULL },
  2650. {
  2651. postgres: `"jsonAttr"->'nested' IS NULL`,
  2652. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`,
  2653. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) IS NULL`,
  2654. mysql: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`,
  2655. },
  2656. );
  2657. testSql(
  2658. { 'jsonAttr.nested': { [Op.eq]: null } },
  2659. {
  2660. postgres: `"jsonAttr"->'nested' = 'null'`,
  2661. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = 'null'`,
  2662. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = 'null'`,
  2663. mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('null' AS JSON)`,
  2664. },
  2665. );
  2666. testSql(
  2667. { 'jsonAttr.nested': { [Op.is]: null } },
  2668. {
  2669. postgres: `"jsonAttr"->'nested' IS NULL`,
  2670. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`,
  2671. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) IS NULL`,
  2672. mysql: `json_extract(\`jsonAttr\`,'$.nested') IS NULL`,
  2673. },
  2674. );
  2675. testSql(where('value', Op.eq, attribute('jsonAttr.nested')), {
  2676. postgres: `'"value"' = "jsonAttr"->'nested'`,
  2677. sqlite3: `'"value"' = json_extract(\`jsonAttr\`,'$.nested')`,
  2678. mariadb: `'"value"' = json_compact(json_extract(\`jsonAttr\`,'$.nested'))`,
  2679. mysql: `CAST('"value"' AS JSON) = json_extract(\`jsonAttr\`,'$.nested')`,
  2680. });
  2681. testSql(
  2682. { 'jsonAttr.nested.twice': 'value' },
  2683. {
  2684. postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`,
  2685. sqlite3: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`,
  2686. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`,
  2687. mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`,
  2688. },
  2689. );
  2690. testSql(
  2691. {
  2692. jsonAttr: { nested: 'value' },
  2693. },
  2694. {
  2695. postgres: `"jsonAttr"->'nested' = '"value"'`,
  2696. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`,
  2697. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`,
  2698. mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`,
  2699. },
  2700. );
  2701. testSql(
  2702. {
  2703. 'jsonAttr.nested': { twice: 'value' },
  2704. },
  2705. {
  2706. postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`,
  2707. sqlite3: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`,
  2708. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`,
  2709. mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`,
  2710. },
  2711. );
  2712. testSql(
  2713. {
  2714. jsonAttr: { [Op.eq]: { key: 'value' } },
  2715. },
  2716. {
  2717. default: `[jsonAttr] = '{"key":"value"}'`,
  2718. mysql: `\`jsonAttr\` = CAST('{"key":"value"}' AS JSON)`,
  2719. },
  2720. );
  2721. testSql(
  2722. {
  2723. 'jsonAttr.nested': { [Op.ne]: 'value' },
  2724. },
  2725. {
  2726. postgres: `"jsonAttr"->'nested' != '"value"'`,
  2727. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') != '"value"'`,
  2728. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) != '"value"'`,
  2729. mysql: `json_extract(\`jsonAttr\`,'$.nested') != CAST('"value"' AS JSON)`,
  2730. },
  2731. );
  2732. testSql(
  2733. {
  2734. '$jsonAttr$.nested': 'value',
  2735. },
  2736. {
  2737. postgres: `"jsonAttr"->'nested' = '"value"'`,
  2738. sqlite3: `json_extract(\`jsonAttr\`,'$.nested') = '"value"'`,
  2739. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested')) = '"value"'`,
  2740. mysql: `json_extract(\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`,
  2741. },
  2742. );
  2743. testSql(
  2744. {
  2745. '$association.jsonAttr$.nested': 'value',
  2746. },
  2747. {
  2748. postgres: `"association"."jsonAttr"->'nested' = '"value"'`,
  2749. sqlite3: `json_extract(\`association\`.\`jsonAttr\`,'$.nested') = '"value"'`,
  2750. mariadb: `json_compact(json_extract(\`association\`.\`jsonAttr\`,'$.nested')) = '"value"'`,
  2751. mysql: `json_extract(\`association\`.\`jsonAttr\`,'$.nested') = CAST('"value"' AS JSON)`,
  2752. },
  2753. );
  2754. testSql(
  2755. {
  2756. 'jsonAttr.nested::STRING': 'value',
  2757. },
  2758. {
  2759. // with the left value cast to a string, we serialize the right value as a string, not as a JSON value
  2760. postgres: `CAST("jsonAttr"->'nested' AS STRING) = 'value'`,
  2761. mariadb: `CAST(json_compact(json_extract(\`jsonAttr\`,'$.nested')) AS STRING) = 'value'`,
  2762. 'sqlite3 mysql': `CAST(json_extract(\`jsonAttr\`,'$.nested') AS STRING) = 'value'`,
  2763. },
  2764. );
  2765. testSql(
  2766. {
  2767. '$association.jsonAttr$.nested::STRING': {
  2768. attribute: 'value',
  2769. },
  2770. },
  2771. {
  2772. default: new Error(`Could not guess type of value { attribute: 'value' }`),
  2773. },
  2774. );
  2775. testSql(
  2776. {
  2777. '$association.jsonAttr$.nested.deep::STRING': 'value',
  2778. },
  2779. {
  2780. postgres: `CAST("association"."jsonAttr"#>ARRAY['nested','deep']::VARCHAR(255)[] AS STRING) = 'value'`,
  2781. mariadb: `CAST(json_compact(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep')) AS STRING) = 'value'`,
  2782. 'sqlite3 mysql': `CAST(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep') AS STRING) = 'value'`,
  2783. },
  2784. );
  2785. testSql(
  2786. {
  2787. $jsonAttr$: { 'nested::string': 'value' },
  2788. },
  2789. {
  2790. postgres: `CAST("jsonAttr"->'nested' AS STRING) = 'value'`,
  2791. mariadb: `CAST(json_compact(json_extract(\`jsonAttr\`,'$.nested')) AS STRING) = 'value'`,
  2792. 'sqlite3 mysql': `CAST(json_extract(\`jsonAttr\`,'$.nested') AS STRING) = 'value'`,
  2793. },
  2794. );
  2795. testSql(
  2796. { 'jsonAttr.nested.attribute': 4 },
  2797. {
  2798. postgres: `"jsonAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`,
  2799. sqlite3: `json_extract(\`jsonAttr\`,'$.nested.attribute') = '4'`,
  2800. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.attribute')) = '4'`,
  2801. mysql: `json_extract(\`jsonAttr\`,'$.nested.attribute') = CAST('4' AS JSON)`,
  2802. },
  2803. );
  2804. // 0 is treated as a string key here, not an array index
  2805. testSql(
  2806. { 'jsonAttr.0': 4 },
  2807. {
  2808. postgres: `"jsonAttr"->'0' = '4'`,
  2809. sqlite3: `json_extract(\`jsonAttr\`,'$."0"') = '4'`,
  2810. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."0"')) = '4'`,
  2811. mysql: `json_extract(\`jsonAttr\`,'$."0"') = CAST('4' AS JSON)`,
  2812. },
  2813. );
  2814. // 0 is treated as an index here, not a string key
  2815. testSql(
  2816. { 'jsonAttr[0]': 4 },
  2817. {
  2818. postgres: `"jsonAttr"->0 = '4'`,
  2819. // these tests cannot be deduplicated because [0] will be replaced by `0` by expectsql
  2820. sqlite3: `json_extract(\`jsonAttr\`,'$[0]') = '4'`,
  2821. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$[0]')) = '4'`,
  2822. mysql: `json_extract(\`jsonAttr\`,'$[0]') = CAST('4' AS JSON)`,
  2823. },
  2824. );
  2825. testSql(
  2826. { 'jsonAttr.0.attribute': 4 },
  2827. {
  2828. postgres: `"jsonAttr"#>ARRAY['0','attribute']::VARCHAR(255)[] = '4'`,
  2829. sqlite3: `json_extract(\`jsonAttr\`,'$."0".attribute') = '4'`,
  2830. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."0".attribute')) = '4'`,
  2831. mysql: `json_extract(\`jsonAttr\`,'$."0".attribute') = CAST('4' AS JSON)`,
  2832. },
  2833. );
  2834. // Regression test: https://github.com/sequelize/sequelize/issues/8718
  2835. testSql(
  2836. { jsonAttr: { 'hyphenated-key': 4 } },
  2837. {
  2838. postgres: `"jsonAttr"->'hyphenated-key' = '4'`,
  2839. sqlite3: `json_extract(\`jsonAttr\`,'$."hyphenated-key"') = '4'`,
  2840. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."hyphenated-key"')) = '4'`,
  2841. mysql: `json_extract(\`jsonAttr\`,'$."hyphenated-key"') = CAST('4' AS JSON)`,
  2842. },
  2843. );
  2844. // SQL injection test
  2845. testSql(
  2846. { jsonAttr: { '"a\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "': 1 } },
  2847. {
  2848. postgres: `"jsonAttr"->'a'')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ' = '1'`,
  2849. mysql: `json_extract(\`jsonAttr\`,'$."a\\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "') = CAST('1' AS JSON)`,
  2850. sqlite3: `json_extract(\`jsonAttr\`,'$."a'')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "') = '1'`,
  2851. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."a\\')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- "')) = '1'`,
  2852. },
  2853. );
  2854. testSql(
  2855. { 'jsonAttr[0].nested.attribute': 4 },
  2856. {
  2857. postgres: `"jsonAttr"#>ARRAY['0','nested','attribute']::VARCHAR(255)[] = '4'`,
  2858. // these tests cannot be deduplicated because [0] will be replaced by `0` by expectsql
  2859. sqlite3: `json_extract(\`jsonAttr\`,'$[0].nested.attribute') = '4'`,
  2860. mariadb: `json_compact(json_extract(\`jsonAttr\`,'$[0].nested.attribute')) = '4'`,
  2861. mysql: `json_extract(\`jsonAttr\`,'$[0].nested.attribute') = CAST('4' AS JSON)`,
  2862. },
  2863. );
  2864. // aliases attribute -> column correctly
  2865. testSql(
  2866. { 'aliasedJsonAttr.nested.attribute': 4 },
  2867. {
  2868. postgres: `"aliased_json"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`,
  2869. sqlite3: `json_extract(\`aliased_json\`,'$.nested.attribute') = '4'`,
  2870. mariadb: `json_compact(json_extract(\`aliased_json\`,'$.nested.attribute')) = '4'`,
  2871. mysql: `json_extract(\`aliased_json\`,'$.nested.attribute') = CAST('4' AS JSON)`,
  2872. },
  2873. );
  2874. }
  2875. if (dialectSupportsJsonUnquotedExtraction()) {
  2876. testSql(
  2877. { 'jsonAttr:unquote': 0 },
  2878. {
  2879. postgres: `"jsonAttr"#>>ARRAY[]::TEXT[] = 0`,
  2880. mssql: `JSON_VALUE([jsonAttr]) = 0`,
  2881. 'sqlite3 mysql mariadb': `json_unquote([jsonAttr]) = 0`,
  2882. },
  2883. );
  2884. testSql(
  2885. { 'jsonAttr.key:unquote': 0 },
  2886. {
  2887. postgres: `"jsonAttr"->>'key' = 0`,
  2888. mssql: `JSON_VALUE([jsonAttr], N'$.key') = 0`,
  2889. 'sqlite3 mysql mariadb': `json_unquote(json_extract([jsonAttr],'$.key')) = 0`,
  2890. },
  2891. );
  2892. testSql(
  2893. { 'jsonAttr.nested.key:unquote': 0 },
  2894. {
  2895. postgres: `"jsonAttr"#>>ARRAY['nested','key']::VARCHAR(255)[] = 0`,
  2896. mssql: `JSON_VALUE([jsonAttr], N'$.nested.key') = 0`,
  2897. 'sqlite3 mysql mariadb': `json_unquote(json_extract([jsonAttr],'$.nested.key')) = 0`,
  2898. },
  2899. );
  2900. testSql(
  2901. { 'jsonAttr[0]:unquote': 0 },
  2902. {
  2903. postgres: `"jsonAttr"->>0 = 0`,
  2904. // must be separate because [0] will be replaced by `0` by expectsql
  2905. sqlite3: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`,
  2906. mysql: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`,
  2907. mariadb: `json_unquote(json_extract(\`jsonAttr\`,'$[0]')) = 0`,
  2908. mssql: `JSON_VALUE([jsonAttr], N'$[0]') = 0`,
  2909. },
  2910. );
  2911. }
  2912. });
  2913. }
  2914. if (dialectSupportsJsonB()) {
  2915. describe('JSONB', () => {
  2916. testSql(
  2917. {
  2918. jsonbAttr: {
  2919. [Op.anyKeyExists]: ['a', 'b'],
  2920. },
  2921. },
  2922. {
  2923. default: `[jsonbAttr] ?| ARRAY['a','b']`,
  2924. },
  2925. );
  2926. testSql(
  2927. {
  2928. jsonbAttr: {
  2929. [Op.allKeysExist]: ['a', 'b'],
  2930. },
  2931. },
  2932. {
  2933. default: `[jsonbAttr] ?& ARRAY['a','b']`,
  2934. },
  2935. );
  2936. testSql(
  2937. {
  2938. jsonbAttr: {
  2939. [Op.anyKeyExists]: literal(
  2940. `ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`,
  2941. ),
  2942. },
  2943. },
  2944. {
  2945. default: `[jsonbAttr] ?| ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`,
  2946. },
  2947. );
  2948. testSql(
  2949. {
  2950. jsonbAttr: {
  2951. [Op.allKeysExist]: literal(
  2952. `ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`,
  2953. ),
  2954. },
  2955. },
  2956. {
  2957. default: `[jsonbAttr] ?& ARRAY(SELECT jsonb_array_elements_text('ARRAY["a","b"]'))`,
  2958. },
  2959. );
  2960. testSql(
  2961. {
  2962. jsonbAttr: {
  2963. [Op.anyKeyExists]: col('label'),
  2964. },
  2965. },
  2966. {
  2967. default: `[jsonbAttr] ?| "label"`,
  2968. },
  2969. );
  2970. testSql(
  2971. {
  2972. jsonbAttr: {
  2973. [Op.allKeysExist]: col('labels'),
  2974. },
  2975. },
  2976. {
  2977. default: `[jsonbAttr] ?& "labels"`,
  2978. },
  2979. );
  2980. testSql(
  2981. {
  2982. jsonbAttr: {
  2983. [Op.anyKeyExists]: cast(col('labels'), 'STRING[]'),
  2984. },
  2985. },
  2986. {
  2987. default: `[jsonbAttr] ?| CAST("labels" AS STRING[])`,
  2988. },
  2989. );
  2990. testSql(
  2991. {
  2992. jsonbAttr: {
  2993. [Op.allKeysExist]: cast(col('labels'), 'STRING[]'),
  2994. },
  2995. },
  2996. {
  2997. default: `[jsonbAttr] ?& CAST("labels" AS STRING[])`,
  2998. },
  2999. );
  3000. testSql(
  3001. {
  3002. jsonbAttr: {
  3003. [Op.anyKeyExists]: [],
  3004. },
  3005. },
  3006. {
  3007. default: `[jsonbAttr] ?| ARRAY[]::TEXT[]`,
  3008. },
  3009. );
  3010. testSql(
  3011. {
  3012. jsonbAttr: {
  3013. [Op.allKeysExist]: [],
  3014. },
  3015. },
  3016. {
  3017. default: `[jsonbAttr] ?& ARRAY[]::TEXT[]`,
  3018. },
  3019. );
  3020. testSql(
  3021. {
  3022. jsonbAttr: {
  3023. [Op.anyKeyExists]: fn('get_label'),
  3024. },
  3025. },
  3026. {
  3027. default: `[jsonbAttr] ?| get_label()`,
  3028. },
  3029. );
  3030. testSql(
  3031. {
  3032. jsonbAttr: {
  3033. [Op.allKeysExist]: fn('get_labels'),
  3034. },
  3035. },
  3036. {
  3037. default: `[jsonbAttr] ?& get_labels()`,
  3038. },
  3039. );
  3040. testSql(
  3041. // @ts-expect-error -- typings for `json` are broken, but `json()` is deprecated
  3042. { id: { [Op.eq]: json('profile.id') } },
  3043. {
  3044. default: `"id" = "profile"->'id'`,
  3045. },
  3046. );
  3047. testSql(
  3048. // @ts-expect-error -- typings for `json` are broken, but `json()` is deprecated
  3049. json('profile.id', cast('12346-78912', 'text')),
  3050. {
  3051. postgres: `"User"."profile"->'id' = CAST('12346-78912' AS TEXT)`,
  3052. },
  3053. {
  3054. mainAlias: 'User',
  3055. },
  3056. );
  3057. testSql(
  3058. json({ profile: { id: '12346-78912', name: 'test' } }),
  3059. {
  3060. postgres: `"User"."profile"->'id' = '"12346-78912"' AND "User"."profile"->'name' = '"test"'`,
  3061. },
  3062. {
  3063. mainAlias: 'User',
  3064. },
  3065. );
  3066. testSql(
  3067. {
  3068. jsonbAttr: {
  3069. nested: {
  3070. attribute: 'value',
  3071. },
  3072. },
  3073. },
  3074. {
  3075. postgres: `"User"."jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '"value"'`,
  3076. },
  3077. {
  3078. mainAlias: 'User',
  3079. },
  3080. );
  3081. testSql(
  3082. {
  3083. jsonbAttr: {
  3084. nested: {
  3085. [Op.in]: [1, 2],
  3086. },
  3087. },
  3088. },
  3089. {
  3090. postgres: `"jsonbAttr"->'nested' IN ('1', '2')`,
  3091. },
  3092. );
  3093. testSql(
  3094. {
  3095. 'jsonbAttr.nested.attribute': {
  3096. [Op.in]: [3, 7],
  3097. },
  3098. },
  3099. {
  3100. postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] IN ('3', '7')`,
  3101. },
  3102. );
  3103. testSql(
  3104. {
  3105. jsonbAttr: {
  3106. nested: {
  3107. [Op.between]: [1, 2],
  3108. },
  3109. },
  3110. },
  3111. {
  3112. postgres: `"jsonbAttr"->'nested' BETWEEN '1' AND '2'`,
  3113. },
  3114. );
  3115. testSql(
  3116. {
  3117. jsonbAttr: {
  3118. price: 5,
  3119. name: 'Product',
  3120. },
  3121. },
  3122. {
  3123. postgres: `"jsonbAttr"->'price' = '5' AND "jsonbAttr"->'name' = '"Product"'`,
  3124. },
  3125. );
  3126. testSql(
  3127. {
  3128. jsonbAttr: {
  3129. name: {
  3130. last: 'Simpson',
  3131. },
  3132. employment: {
  3133. [Op.ne]: 'None',
  3134. },
  3135. },
  3136. },
  3137. {
  3138. postgres: `"User"."jsonbAttr"#>ARRAY['name','last']::VARCHAR(255)[] = '"Simpson"' AND "User"."jsonbAttr"->'employment' != '"None"'`,
  3139. },
  3140. {
  3141. mainAlias: 'User',
  3142. },
  3143. );
  3144. const dt = new Date();
  3145. const jsonDt = JSON.stringify(dt);
  3146. testSql(
  3147. {
  3148. jsonbAttr: {
  3149. nested: {
  3150. attribute: {
  3151. [Op.gt]: dt,
  3152. },
  3153. },
  3154. },
  3155. },
  3156. {
  3157. postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] > ${queryGen.escape(jsonDt)}`,
  3158. },
  3159. );
  3160. testSql(
  3161. {
  3162. jsonbAttr: {
  3163. nested: {
  3164. attribute: true,
  3165. },
  3166. },
  3167. },
  3168. {
  3169. postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = 'true'`,
  3170. },
  3171. );
  3172. testSql(
  3173. {
  3174. jsonbAttr: {
  3175. [Op.contains]: { company: 'Magnafone' },
  3176. },
  3177. },
  3178. {
  3179. default: `[jsonbAttr] @> '{"company":"Magnafone"}'`,
  3180. },
  3181. );
  3182. testSql(
  3183. {
  3184. jsonbTypeLiteralAttr: { [Op.contains]: { foo: 'bar' } },
  3185. },
  3186. {
  3187. postgres: '"jsonbTypeLiteralAttr" @> \'{"foo":"bar"}\'',
  3188. },
  3189. );
  3190. testSql(
  3191. {
  3192. // @ts-expect-error -- key `bad` isn't known
  3193. jsonbTypeLiteralAttr: { [Op.contains]: { bad: 'bad' } },
  3194. },
  3195. {
  3196. postgres: '"jsonbTypeLiteralAttr" @> \'{"bad":"bad"}\'',
  3197. },
  3198. );
  3199. testSql(
  3200. {
  3201. jsonbInterfaceAttr: { [Op.contains]: { foo: 'bar' } },
  3202. },
  3203. {
  3204. postgres: '"jsonbInterfaceAttr" @> \'{"foo":"bar"}\'',
  3205. },
  3206. );
  3207. testSql(
  3208. {
  3209. // @ts-expect-error -- key `bad` isn't known
  3210. jsonbInterfaceAttr: { [Op.contains]: { bad: 'bad' } },
  3211. },
  3212. {
  3213. postgres: '"jsonbInterfaceAttr" @> \'{"bad":"bad"}\'',
  3214. },
  3215. );
  3216. // aliases correctly
  3217. testSql(
  3218. { aliasedJsonbAttr: { key: 'value' } },
  3219. {
  3220. postgres: `"aliased_jsonb"->'key' = '"value"'`,
  3221. },
  3222. );
  3223. });
  3224. }
  3225. testSql(
  3226. {
  3227. stringAttr: 'a project',
  3228. [Op.or]: [{ intAttr1: [1, 2, 3] }, { intAttr1: { [Op.gt]: 10 } }],
  3229. },
  3230. {
  3231. default: "([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10) AND [stringAttr] = 'a project'",
  3232. mssql: "([intAttr1] IN (1, 2, 3) OR [intAttr1] > 10) AND [stringAttr] = N'a project'",
  3233. },
  3234. );
  3235. describe('Op.and', () => {
  3236. it('and() is the same as Op.and', () => {
  3237. expect(util.inspect(and('a', 'b'))).to.deep.equal(util.inspect({ [Op.and]: ['a', 'b'] }));
  3238. });
  3239. testSql(and([]), {
  3240. default: '',
  3241. });
  3242. testSql(and({}), {
  3243. default: '',
  3244. });
  3245. // by default: it already is Op.and
  3246. testSql(
  3247. { intAttr1: 1, intAttr2: 2 },
  3248. {
  3249. default: `[intAttr1] = 1 AND [intAttr2] = 2`,
  3250. },
  3251. );
  3252. // top-level array is Op.and
  3253. testSql([{ intAttr1: 1 }, { intAttr1: 2 }], {
  3254. default: `[intAttr1] = 1 AND [intAttr1] = 2`,
  3255. });
  3256. // $intAttr1$ doesn't override intAttr1
  3257. testSql(
  3258. { intAttr1: 1, $intAttr1$: 2 },
  3259. {
  3260. default: `[intAttr1] = 1 AND [intAttr1] = 2`,
  3261. },
  3262. );
  3263. // can pass a simple object
  3264. testSql(
  3265. { [Op.and]: { intAttr1: 1, intAttr2: 2 } },
  3266. {
  3267. default: `[intAttr1] = 1 AND [intAttr2] = 2`,
  3268. },
  3269. );
  3270. // can pass an array
  3271. testSql(
  3272. { [Op.and]: [{ intAttr1: 1, intAttr2: 2 }, { stringAttr: '' }] },
  3273. {
  3274. default: `([intAttr1] = 1 AND [intAttr2] = 2) AND [stringAttr] = ''`,
  3275. mssql: `([intAttr1] = 1 AND [intAttr2] = 2) AND [stringAttr] = N''`,
  3276. },
  3277. );
  3278. // can be used on attribute
  3279. testSql(
  3280. { intAttr1: { [Op.and]: [1, { [Op.gt]: 1 }] } },
  3281. {
  3282. default: `[intAttr1] = 1 AND [intAttr1] > 1`,
  3283. },
  3284. );
  3285. testSql(
  3286. // @ts-expect-error -- cannot be used after operator
  3287. { intAttr1: { [Op.gt]: { [Op.and]: [1, 2] } } },
  3288. {
  3289. default: new Error(`{ [Symbol(and)]: [ 1, 2 ] } is not a valid integer`),
  3290. },
  3291. );
  3292. });
  3293. describe('Op.or', () => {
  3294. it('or() is the same as Op.or', () => {
  3295. expect(util.inspect(or('a', 'b'))).to.deep.equal(util.inspect({ [Op.or]: ['a', 'b'] }));
  3296. });
  3297. testSql(or([]), {
  3298. default: '',
  3299. });
  3300. testSql(or({}), {
  3301. default: '',
  3302. });
  3303. // can pass a simple object
  3304. testSql(
  3305. { [Op.or]: { intAttr1: 1, intAttr2: 2 } },
  3306. {
  3307. default: `[intAttr1] = 1 OR [intAttr2] = 2`,
  3308. },
  3309. );
  3310. // can pass an array
  3311. testSql(
  3312. { [Op.or]: [{ intAttr1: 1, intAttr2: 2 }, { stringAttr: '' }] },
  3313. {
  3314. default: `([intAttr1] = 1 AND [intAttr2] = 2) OR [stringAttr] = ''`,
  3315. mssql: `([intAttr1] = 1 AND [intAttr2] = 2) OR [stringAttr] = N''`,
  3316. },
  3317. );
  3318. // can be used on attribute
  3319. testSql(
  3320. { intAttr1: { [Op.or]: [1, { [Op.gt]: 1 }] } },
  3321. {
  3322. default: `[intAttr1] = 1 OR [intAttr1] > 1`,
  3323. },
  3324. );
  3325. testSql(
  3326. // @ts-expect-error -- cannot be used after operator
  3327. { intAttr1: { [Op.gt]: { [Op.or]: [1, 2] } } },
  3328. {
  3329. default: new Error(`{ [Symbol(or)]: [ 1, 2 ] } is not a valid integer`),
  3330. },
  3331. );
  3332. testSql(
  3333. {
  3334. [Op.or]: {
  3335. intAttr1: [1, 3],
  3336. intAttr2: {
  3337. [Op.in]: [2, 4],
  3338. },
  3339. },
  3340. },
  3341. {
  3342. default: '[intAttr1] IN (1, 3) OR [intAttr2] IN (2, 4)',
  3343. },
  3344. );
  3345. });
  3346. describe('Op.{and,or,not} combinations', () => {
  3347. // both can be used in the same object
  3348. testSql(
  3349. {
  3350. [Op.and]: { intAttr1: 1, intAttr2: 2 },
  3351. [Op.or]: { intAttr1: 1, intAttr2: 2 },
  3352. },
  3353. {
  3354. default: `([intAttr1] = 1 AND [intAttr2] = 2) AND ([intAttr1] = 1 OR [intAttr2] = 2)`,
  3355. },
  3356. );
  3357. // Op.or only applies to its direct Array, the nested array is still Op.and
  3358. testSql(
  3359. {
  3360. [Op.or]: [[{ intAttr1: 1 }, { intAttr1: 2 }], { intAttr1: 3 }],
  3361. },
  3362. {
  3363. default: '([intAttr1] = 1 AND [intAttr1] = 2) OR [intAttr1] = 3',
  3364. },
  3365. );
  3366. // can be nested *after* attribute
  3367. testSql(
  3368. {
  3369. intAttr1: {
  3370. [Op.and]: [1, 2, { [Op.or]: [3, 4] }, { [Op.not]: 5 }, [6, 7]],
  3371. },
  3372. },
  3373. {
  3374. default:
  3375. '[intAttr1] = 1 AND [intAttr1] = 2 AND ([intAttr1] = 3 OR [intAttr1] = 4) AND NOT ([intAttr1] = 5) AND [intAttr1] IN (6, 7)',
  3376. },
  3377. );
  3378. // can be nested
  3379. testSql(
  3380. {
  3381. [Op.not]: {
  3382. [Op.and]: {
  3383. [Op.or]: {
  3384. [Op.and]: {
  3385. intAttr1: 1,
  3386. intAttr2: 2,
  3387. },
  3388. },
  3389. },
  3390. },
  3391. },
  3392. {
  3393. default: 'NOT ([intAttr1] = 1 AND [intAttr2] = 2)',
  3394. },
  3395. );
  3396. testSql(
  3397. {
  3398. [Op.not]: {
  3399. [Op.or]: {
  3400. [Op.and]: {
  3401. intAttr1: 1,
  3402. intAttr2: 2,
  3403. },
  3404. [Op.or]: {
  3405. intAttr1: 1,
  3406. intAttr2: 2,
  3407. },
  3408. },
  3409. },
  3410. },
  3411. {
  3412. default:
  3413. 'NOT (([intAttr1] = 1 AND [intAttr2] = 2) OR ([intAttr1] = 1 OR [intAttr2] = 2))',
  3414. },
  3415. );
  3416. // Op.not, Op.and, Op.or can reside on the same object as attributes
  3417. testSql(
  3418. {
  3419. intAttr1: 1,
  3420. [Op.not]: {
  3421. intAttr1: { [Op.eq]: 2 },
  3422. [Op.and]: {
  3423. intAttr1: 3,
  3424. [Op.or]: {
  3425. intAttr1: 4,
  3426. [Op.and]: {
  3427. intAttr1: 5,
  3428. intAttr2: 6,
  3429. },
  3430. },
  3431. },
  3432. },
  3433. },
  3434. {
  3435. default:
  3436. '(NOT (((([intAttr1] = 5 AND [intAttr2] = 6) OR [intAttr1] = 4) AND [intAttr1] = 3) AND [intAttr1] = 2)) AND [intAttr1] = 1',
  3437. },
  3438. );
  3439. });
  3440. describe('where()', () => {
  3441. {
  3442. // @ts-expect-error -- 'intAttr1' is not a boolean and cannot be compared to the output of 'where'
  3443. const ignore: TestModelWhere = { intAttr1: where(fn('lower', col('name')), null) };
  3444. }
  3445. testSql(
  3446. { booleanAttr: where(fn('lower', col('name')), null) },
  3447. {
  3448. default: `[booleanAttr] = (lower([name]) IS NULL)`,
  3449. },
  3450. );
  3451. testSql(
  3452. { booleanAttr: where(fn('lower', col('name')), null) },
  3453. {
  3454. default: `[booleanAttr] = (lower([name]) IS NULL)`,
  3455. },
  3456. );
  3457. describe('where(leftOperand, operator, rightOperand)', () => {
  3458. testSql(where(col('name'), Op.eq, fn('NOW')), {
  3459. default: '[name] = NOW()',
  3460. });
  3461. // some dialects support having a filter inside aggregate functions:
  3462. // https://github.com/sequelize/sequelize/issues/6666
  3463. testSql(where(fn('sum', { id: 1 }), Op.eq, 1), {
  3464. default: 'sum([id] = 1) = 1',
  3465. });
  3466. // some dialects support having a filter inside aggregate functions, but require casting:
  3467. // https://github.com/sequelize/sequelize/issues/6666
  3468. testSql(where(fn('sum', cast({ id: 1 }, 'int')), Op.eq, 1), {
  3469. default: 'sum(CAST(([id] = 1) AS INT)) = 1',
  3470. });
  3471. // comparing the output of `where` to `where`
  3472. testSql(where(where(col('col'), Op.eq, '1'), Op.eq, where(col('col'), Op.eq, '2')), {
  3473. default: `([col] = '1') = ([col] = '2')`,
  3474. mssql: `([col] = N'1') = ([col] = N'2')`,
  3475. });
  3476. testSql(where(1, Op.eq, 2), {
  3477. default: '1 = 2',
  3478. });
  3479. testSql(where(1, Op.eq, col('col')), {
  3480. default: '1 = [col]',
  3481. });
  3482. testSql(where('string', Op.eq, col('col')), {
  3483. default: `'string' = [col]`,
  3484. mssql: `N'string' = [col]`,
  3485. });
  3486. testSql(where('a', Op.eq, 'b'), {
  3487. default: `'a' = 'b'`,
  3488. mssql: `N'a' = N'b'`,
  3489. });
  3490. it('does not allow string operators', () => {
  3491. // @ts-expect-error -- testing that this errors
  3492. expect(() => where(fn('SUM', col('hours')), '>', 0)).to.throw(
  3493. 'where(left, operator, right) does not accept a string as the operator',
  3494. );
  3495. });
  3496. testSql(where(fn('SUM', col('hours')), Op.gt, 0), {
  3497. default: 'SUM([hours]) > 0',
  3498. });
  3499. testSql(where(fn('lower', col('name')), Op.ne, null), {
  3500. default: 'lower([name]) IS NOT NULL',
  3501. });
  3502. // @ts-expect-error -- While these are supported for backwards compatibility, they are not documented. Users should use isNot
  3503. testSql(where(fn('lower', col('name')), Op.not, null), {
  3504. default: 'NOT (lower([name]) IS NULL)',
  3505. });
  3506. testSql(where(fn('lower', col('name')), Op.isNot, null), {
  3507. default: 'lower([name]) IS NOT NULL',
  3508. });
  3509. testSql(where(col('hours'), Op.between, [0, 5]), {
  3510. default: '[hours] BETWEEN 0 AND 5',
  3511. });
  3512. testSql(where(col('hours'), Op.notBetween, [0, 5]), {
  3513. default: '[hours] NOT BETWEEN 0 AND 5',
  3514. });
  3515. testSql(where({ [Op.col]: 'hours' }, Op.notBetween, [0, 5]), {
  3516. default: '[hours] NOT BETWEEN 0 AND 5',
  3517. });
  3518. testSql(where(cast({ [Op.col]: 'hours' }, 'integer'), Op.notBetween, [0, 5]), {
  3519. default: 'CAST([hours] AS INTEGER) NOT BETWEEN 0 AND 5',
  3520. });
  3521. testSql(where(fn('SUM', { [Op.col]: 'hours' }), Op.notBetween, [0, 5]), {
  3522. default: 'SUM([hours]) NOT BETWEEN 0 AND 5',
  3523. });
  3524. testSql(where(literal(`'hours'`), Op.eq, 'hours'), {
  3525. default: `'hours' = 'hours'`,
  3526. mssql: `'hours' = N'hours'`,
  3527. });
  3528. testSql(where(col('col'), Op.eq, { [Op.in]: [1, 2] }), {
  3529. default: new Error('Could not guess type of value { [Symbol(in)]: [ 1, 2 ] }'),
  3530. });
  3531. });
  3532. describe('where(leftOperand, whereAttributeHashValue)', () => {
  3533. testSql(where(fn('lower', col('name')), null), {
  3534. default: 'lower([name]) IS NULL',
  3535. });
  3536. testSql(where(cast(col('name'), 'int'), { [Op.eq]: 10 }), {
  3537. default: 'CAST([name] AS INT) = 10',
  3538. });
  3539. testSql(where(literal('abc'), { [Op.eq]: 10 }), {
  3540. default: 'abc = 10',
  3541. });
  3542. testSql(where(col('name'), { [Op.eq]: '123', [Op.not]: { [Op.eq]: '456' } }), {
  3543. default: `[name] = '123' AND NOT ([name] = '456')`,
  3544. mssql: `[name] = N'123' AND NOT ([name] = N'456')`,
  3545. });
  3546. testSql(where(col('name'), or({ [Op.eq]: '123', [Op.not]: { [Op.eq]: '456' } })), {
  3547. default: `[name] = '123' OR NOT ([name] = '456')`,
  3548. mssql: `[name] = N'123' OR NOT ([name] = N'456')`,
  3549. });
  3550. testSql(
  3551. // Note: using `col()`, the following is not treated as a json.path.
  3552. // (yes, it's inconsistant with regular attribute notation. attr could be a good replacement)
  3553. where(col('attribute.path'), 10),
  3554. {
  3555. default: '[attribute].[path] = 10',
  3556. },
  3557. );
  3558. testSql(
  3559. // Note: using `col()`, the following is not treated as a nested.attribute.path.
  3560. // (yes, it's inconsistant with regular attribute notation. attr could be a good replacement)
  3561. where(col('$attribute.path$'), 10),
  3562. {
  3563. default: '[$attribute].[path$] = 10',
  3564. },
  3565. );
  3566. testSql(where(col('col'), { [Op.and]: [1, 2] }), {
  3567. default: '[col] = 1 AND [col] = 2',
  3568. });
  3569. if (dialectSupportsJsonOperations() && dialectSupportsJsonQuotedExtraction()) {
  3570. testSql(where(col('col'), { jsonPath: 'value' }), {
  3571. postgres: `"col"->'jsonPath' = '"value"'`,
  3572. sqlite3: `json_extract(\`col\`,'$.jsonPath') = '"value"'`,
  3573. mariadb: `json_compact(json_extract(\`col\`,'$.jsonPath')) = '"value"'`,
  3574. mysql: `json_extract(\`col\`,'$.jsonPath') = CAST('"value"' AS JSON)`,
  3575. });
  3576. }
  3577. });
  3578. });
  3579. });
  3580. });