query.test.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. 'use strict';
  2. const { expect } = require('chai');
  3. const {
  4. DatabaseError,
  5. DataTypes,
  6. ForeignKeyConstraintError,
  7. Sequelize,
  8. sql,
  9. UniqueConstraintError,
  10. } = require('@sequelize/core');
  11. const sinon = require('sinon');
  12. const dayjs = require('dayjs');
  13. const {
  14. allowDeprecationsInSuite,
  15. beforeEach2,
  16. createSequelizeInstance,
  17. createSingleTestSequelizeInstance,
  18. destroySequelizeAfterTest,
  19. getTestDialect,
  20. getTestDialectTeaser,
  21. sequelize,
  22. } = require('../support');
  23. const dialectName = getTestDialect();
  24. const queryGenerator = sequelize.queryGenerator;
  25. const qq = str => {
  26. if (['postgres', 'mssql', 'db2', 'ibmi'].includes(dialectName)) {
  27. return `"${str}"`;
  28. }
  29. if (['mysql', 'mariadb', 'sqlite3'].includes(dialectName)) {
  30. return `\`${str}\``;
  31. }
  32. return str;
  33. };
  34. describe(getTestDialectTeaser('Sequelize'), () => {
  35. allowDeprecationsInSuite(['SEQUELIZE0023']);
  36. describe('query', () => {
  37. afterEach(() => {
  38. console.log.restore && console.log.restore();
  39. });
  40. beforeEach(async function () {
  41. this.User = this.sequelize.define('User', {
  42. username: {
  43. type: DataTypes.STRING,
  44. unique: true,
  45. },
  46. emailAddress: {
  47. type: DataTypes.STRING,
  48. field: 'email_address',
  49. },
  50. });
  51. this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${qq(
  52. 'createdAt',
  53. )}, ${qq(
  54. 'updatedAt',
  55. )}) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  56. if (['db2', 'ibmi'].includes(dialectName)) {
  57. this.insertQuery = `INSERT INTO ${qq(this.User.tableName)}
  58. ("username", "email_address", ${qq('createdAt')}, ${qq('updatedAt')}) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  59. }
  60. await this.User.sync({ force: true });
  61. });
  62. it('executes a query the internal way', async function () {
  63. await this.sequelize.query(this.insertQuery, { raw: true });
  64. });
  65. it('executes a query if only the sql is passed', async function () {
  66. await this.sequelize.query(this.insertQuery);
  67. });
  68. describe('QueryTypes', () => {
  69. it('RAW', async function () {
  70. await this.sequelize.query(this.insertQuery, {
  71. type: Sequelize.QueryTypes.RAW,
  72. });
  73. const [rows, count] = await this.sequelize.query(
  74. `SELECT * FROM ${qq(this.User.tableName)};`,
  75. {
  76. type: Sequelize.QueryTypes.RAW,
  77. },
  78. );
  79. expect(rows).to.be.an.instanceof(Array);
  80. expect(count).to.be.ok;
  81. });
  82. });
  83. describe('retry', () => {
  84. it('properly bind parameters on extra retries', async function () {
  85. const payload = {
  86. username: 'test',
  87. createdAt: '2010-10-10 00:00:00',
  88. updatedAt: '2010-10-10 00:00:00',
  89. };
  90. const spy = sinon.spy();
  91. await this.User.create(payload);
  92. await expect(
  93. this.sequelize.query(
  94. `
  95. INSERT INTO ${qq(this.User.tableName)} (${qq('username')},${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt);
  96. `,
  97. {
  98. bind: payload,
  99. logging: spy,
  100. retry: {
  101. max: 3,
  102. match: [/Validation/],
  103. },
  104. },
  105. ),
  106. ).to.be.rejectedWith(Sequelize.UniqueConstraintError);
  107. expect(spy.callCount).to.eql(['db2', 'ibmi'].includes(dialectName) ? 1 : 3);
  108. });
  109. });
  110. describe('logging', () => {
  111. it('executes a query with global benchmarking option and custom logger', async () => {
  112. const logger = sinon.spy();
  113. const sequelize = createSingleTestSequelizeInstance({
  114. logging: logger,
  115. benchmark: true,
  116. });
  117. await sequelize.query(`select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`);
  118. expect(logger.calledOnce).to.be.true;
  119. expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/);
  120. expect(typeof logger.args[0][1] === 'number').to.be.true;
  121. });
  122. it('executes a query with benchmarking option and custom logger', async function () {
  123. const logger = sinon.spy();
  124. await this.sequelize.query(
  125. `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
  126. {
  127. logging: logger,
  128. benchmark: true,
  129. },
  130. );
  131. expect(logger.calledOnce).to.be.true;
  132. expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/);
  133. expect(typeof logger.args[0][1] === 'number').to.be.true;
  134. });
  135. it('executes a query with queryLabel option and custom logger', async () => {
  136. const logger = sinon.spy();
  137. const sequelize = createSingleTestSequelizeInstance({
  138. logging: logger,
  139. });
  140. await sequelize.query(
  141. `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
  142. {
  143. queryLabel: 'tricky select',
  144. },
  145. );
  146. expect(logger.calledOnce).to.be.true;
  147. expect(logger.args[0][0]).to.be.match(
  148. /^tricky select[\n]Executing \((\d*|default)\): select 1/,
  149. );
  150. });
  151. it('executes a query with empty string, queryLabel option and custom logger', async () => {
  152. const logger = sinon.spy();
  153. const sequelize = createSingleTestSequelizeInstance({
  154. logging: logger,
  155. });
  156. await sequelize.query(
  157. `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
  158. {
  159. queryLabel: '',
  160. },
  161. );
  162. expect(logger.calledOnce).to.be.true;
  163. expect(logger.args[0][0]).to.be.match(/^Executing \((\d*|default)\): select 1/);
  164. });
  165. it('executes a query with benchmarking option, queryLabel option and custom logger', async () => {
  166. const logger = sinon.spy();
  167. const sequelize = createSingleTestSequelizeInstance({
  168. logging: logger,
  169. benchmark: true,
  170. });
  171. await sequelize.query(
  172. `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
  173. {
  174. queryLabel: 'tricky select',
  175. },
  176. );
  177. expect(logger.calledOnce).to.be.true;
  178. expect(logger.args[0][0]).to.be.match(
  179. /^tricky select[\n]Executed \((\d*|default)\): select 1/,
  180. );
  181. });
  182. describe('with logQueryParameters', () => {
  183. const vars = beforeEach2(async () => {
  184. const sequelize = createSequelizeInstance({
  185. benchmark: true,
  186. logQueryParameters: true,
  187. });
  188. const User = sequelize.define(
  189. 'User',
  190. {
  191. id: {
  192. type: DataTypes.INTEGER,
  193. primaryKey: true,
  194. autoIncrement: true,
  195. },
  196. username: {
  197. type: DataTypes.STRING,
  198. },
  199. emailAddress: {
  200. type: DataTypes.STRING,
  201. },
  202. },
  203. {
  204. timestamps: false,
  205. },
  206. );
  207. await User.sync({ force: true });
  208. return { sequelize, User };
  209. });
  210. afterEach(() => {
  211. return vars.sequelize.close();
  212. });
  213. it('add parameters in log sql', async () => {
  214. let createSql;
  215. let updateSql;
  216. const user = await vars.User.create(
  217. {
  218. username: 'john',
  219. emailAddress: 'john@gmail.com',
  220. },
  221. {
  222. logging: s => {
  223. createSql = s;
  224. },
  225. },
  226. );
  227. user.username = 'li';
  228. await user.save({
  229. logging: s => {
  230. updateSql = s;
  231. },
  232. });
  233. if (
  234. dialectName === 'db2' ||
  235. dialectName === 'postgres' ||
  236. dialectName === 'mariadb' ||
  237. dialectName === 'mysql'
  238. ) {
  239. // these dialects use positional bind parameters
  240. expect(createSql.endsWith(` with parameters [ 'john', 'john@gmail.com' ]`)).to.eq(
  241. true,
  242. 'bind parameters incorrectly logged for INSERT query',
  243. );
  244. expect(updateSql.endsWith(` with parameters [ 'li', 1 ]`)).to.eq(
  245. true,
  246. 'bind parameters incorrectly logged for UPDATE query',
  247. );
  248. } else {
  249. // these dialects use named bind parameters
  250. expect(
  251. createSql.endsWith(
  252. ` with parameters { sequelize_1: 'john', sequelize_2: 'john@gmail.com' }`,
  253. ),
  254. ).to.eq(true, 'bind parameters incorrectly logged for INSERT query');
  255. expect(
  256. updateSql.endsWith(` with parameters { sequelize_1: 'li', sequelize_2: 1 }`),
  257. ).to.eq(true, 'bind parameters incorrectly logged for UPDATE query');
  258. }
  259. });
  260. it('add parameters in log sql when use bind value', async () => {
  261. let logSql;
  262. let typeCast = dialectName === 'postgres' ? '::text' : '';
  263. if (['db2'].includes(dialectName)) {
  264. typeCast = '::VARCHAR';
  265. }
  266. await vars.sequelize.query(
  267. `select $1${typeCast} as foo, $2${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  268. {
  269. bind: ['foo', 'bar'],
  270. logging: s => {
  271. logSql = s;
  272. },
  273. },
  274. );
  275. expect(logSql.endsWith(` with parameters [ 'foo', 'bar' ]`)).to.eq(
  276. true,
  277. 'bind parameters incorrectly logged.',
  278. );
  279. });
  280. });
  281. });
  282. it('executes select queries correctly', async function () {
  283. await this.sequelize.query(this.insertQuery);
  284. const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`);
  285. expect(
  286. users.map(u => {
  287. return u.username;
  288. }),
  289. ).to.include('john');
  290. });
  291. it('executes select queries correctly when quoteIdentifiers is false', async function () {
  292. const sequelize = createSequelizeInstance({
  293. quoteIdentifiers: false,
  294. });
  295. destroySequelizeAfterTest(sequelize);
  296. await sequelize.query(this.insertQuery);
  297. const [users] = await sequelize.query(`select * from ${qq(this.User.tableName)}`);
  298. expect(
  299. users.map(u => {
  300. return u.username;
  301. }),
  302. ).to.include('john');
  303. });
  304. it('executes select query with dot notation results', async function () {
  305. await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`);
  306. await this.sequelize.query(this.insertQuery);
  307. const [users] = await this.sequelize.query(
  308. `select ${qq('username')} as ${qq('user.username')} from ${qq(this.User.tableName)}`,
  309. );
  310. expect(users).to.deep.equal([{ 'user.username': 'john' }]);
  311. });
  312. it('executes select query with dot notation results and nest it', async function () {
  313. await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`);
  314. await this.sequelize.query(this.insertQuery);
  315. const users = await this.sequelize.query(
  316. `select ${qq('username')} as ${qq('user.username')} from ${qq(this.User.tableName)}`,
  317. { raw: true, nest: true },
  318. );
  319. expect(
  320. users.map(u => {
  321. return u.user;
  322. }),
  323. ).to.deep.equal([{ username: 'john' }]);
  324. });
  325. if (dialectName === 'mysql') {
  326. it('executes stored procedures', async function () {
  327. await this.sequelize.query(this.insertQuery);
  328. await this.sequelize.query('DROP PROCEDURE IF EXISTS foo');
  329. await this.sequelize.query(`CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};`);
  330. const users = await this.sequelize.query('CALL foo()');
  331. expect(
  332. users.map(u => {
  333. return u.username;
  334. }),
  335. ).to.include('john');
  336. });
  337. } else if (dialectName === 'db2') {
  338. it('executes stored procedures', async function () {
  339. const { sequelize } = this;
  340. await sequelize.query(this.insertQuery);
  341. try {
  342. await sequelize.query('DROP PROCEDURE foo');
  343. } catch (error) {
  344. // DB2 does not support DROP PROCEDURE IF EXISTS
  345. // -204 means "FOO" does not exist
  346. // https://www.ibm.com/docs/en/db2-for-zos/11?topic=sec-204
  347. if (error.cause.sqlcode !== -204) {
  348. throw error;
  349. }
  350. }
  351. await sequelize.query(
  352. `CREATE PROCEDURE foo() DYNAMIC RESULT SETS 1 LANGUAGE SQL BEGIN DECLARE cr1 CURSOR WITH RETURN FOR SELECT * FROM ${qq(this.User.tableName)}; OPEN cr1; END`,
  353. );
  354. const users = await sequelize.query('CALL foo()');
  355. expect(users.map(u => u.username)).to.include('john');
  356. });
  357. }
  358. it('uses the passed model', async function () {
  359. await this.sequelize.query(this.insertQuery);
  360. const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
  361. model: this.User,
  362. });
  363. expect(users[0]).to.be.instanceof(this.User);
  364. });
  365. it('maps the field names to attributes based on the passed model', async function () {
  366. await this.sequelize.query(this.insertQuery);
  367. const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
  368. model: this.User,
  369. mapToModel: true,
  370. });
  371. expect(users[0].emailAddress).to.equal('john@gmail.com');
  372. });
  373. it('arbitrarily map the field names', async function () {
  374. await this.sequelize.query(this.insertQuery);
  375. const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
  376. type: 'SELECT',
  377. fieldMap: { username: 'userName', email_address: 'email' },
  378. });
  379. expect(users[0].userName).to.equal('john');
  380. expect(users[0].email).to.equal('john@gmail.com');
  381. });
  382. it('keeps field names that are mapped to the same name', async function () {
  383. await this.sequelize.query(this.insertQuery);
  384. const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
  385. type: 'SELECT',
  386. fieldMap: { username: 'username', email_address: 'email' },
  387. });
  388. expect(users[0].username).to.equal('john');
  389. expect(users[0].email).to.equal('john@gmail.com');
  390. });
  391. // Only run stacktrace tests on Node 12+, since only Node 12+ supports
  392. // async stacktraces
  393. const nodeVersionMatch = process.version.match(/^v(\d+)/);
  394. let nodeMajorVersion = 0;
  395. if (nodeVersionMatch && nodeVersionMatch[1]) {
  396. nodeMajorVersion = Number.parseInt(nodeVersionMatch[1], 10);
  397. }
  398. if (nodeMajorVersion >= 12) {
  399. describe('stacktraces', () => {
  400. beforeEach(async function () {
  401. this.UserVisit = this.sequelize.define(
  402. 'UserVisit',
  403. {
  404. userId: {
  405. type: DataTypes.STRING,
  406. field: 'user_id',
  407. },
  408. visitedAt: {
  409. type: DataTypes.DATE,
  410. field: 'visited_at',
  411. },
  412. },
  413. {
  414. indexes: [{ name: 'user_id', fields: ['user_id'] }],
  415. },
  416. );
  417. this.User.hasMany(this.UserVisit, { foreignKey: 'user_id' });
  418. await this.UserVisit.sync({ force: true });
  419. });
  420. it('emits raw errors if requested', async function () {
  421. const sql = 'SELECT 1 FROM NotFoundTable';
  422. await expect(
  423. this.sequelize.query(sql, { rawErrors: false }),
  424. ).to.eventually.be.rejectedWith(DatabaseError);
  425. await expect(
  426. this.sequelize.query(sql, { rawErrors: true }),
  427. ).to.eventually.be.rejected.and.not.be.an.instanceOf(DatabaseError);
  428. });
  429. it('emits full stacktraces for generic database error', async function () {
  430. let error = null;
  431. try {
  432. await this.sequelize.query(
  433. `select * from ${qq(this.User.tableName)} where ${qq('unknown_column')} = 1`,
  434. );
  435. } catch (error_) {
  436. error = error_;
  437. }
  438. expect(error).to.be.instanceOf(DatabaseError);
  439. expect(error.stack).to.contain('query.test');
  440. });
  441. it('emits full stacktraces for unique constraint error', async function () {
  442. let query;
  443. if (['db2', 'ibmi'].includes(dialectName)) {
  444. query = `INSERT INTO ${qq(this.User.tableName)} ("username", "email_address", ${qq(
  445. 'createdAt',
  446. )}, ${qq(
  447. 'updatedAt',
  448. )}) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  449. } else {
  450. query = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${qq(
  451. 'createdAt',
  452. )}, ${qq(
  453. 'updatedAt',
  454. )}) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  455. }
  456. let error = null;
  457. try {
  458. // Insert 1 row
  459. await this.sequelize.query(query);
  460. // Try inserting a duplicate row
  461. await this.sequelize.query(query);
  462. } catch (error_) {
  463. error = error_;
  464. }
  465. expect(error).to.be.instanceOf(UniqueConstraintError);
  466. expect(error.stack).to.contain('query.test');
  467. });
  468. it('emits full stacktraces for constraint validation error', async function () {
  469. let error = null;
  470. try {
  471. let query;
  472. if (['db2', 'ibmi'].includes(dialectName)) {
  473. query = `INSERT INTO ${qq(this.UserVisit.tableName)} ("user_id", "visited_at", ${qq(
  474. 'createdAt',
  475. )}, ${qq(
  476. 'updatedAt',
  477. )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  478. } else {
  479. query = `INSERT INTO ${qq(this.UserVisit.tableName)} (user_id, visited_at, ${qq(
  480. 'createdAt',
  481. )}, ${qq(
  482. 'updatedAt',
  483. )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
  484. }
  485. await this.sequelize.query(query);
  486. } catch (error_) {
  487. error = error_;
  488. }
  489. expect(error).to.be.instanceOf(ForeignKeyConstraintError);
  490. expect(error.stack).to.contain('query.test');
  491. });
  492. });
  493. }
  494. describe('rejections', () => {
  495. it('reject if the query is not a string', async function () {
  496. // this is a legacy, removed signature
  497. await this.sequelize
  498. .query(
  499. { query: 'select ? as foo, ? as bar', values: [1, 2] },
  500. { raw: true, replacements: [1, 2] },
  501. )
  502. .should.be.rejectedWith(
  503. Error,
  504. '"sql" cannot be an object. Pass a string instead, and pass bind and replacement parameters through the "options" parameter',
  505. );
  506. });
  507. it('reject when key is missing in the passed object', async function () {
  508. await this.sequelize
  509. .query('select :one as foo, :two as bar, :three as baz', {
  510. raw: true,
  511. replacements: { one: 1, two: 2 },
  512. })
  513. .should.be.rejectedWith(
  514. Error,
  515. /Named replacement ":\w+" has no entry in the replacement map\./g,
  516. );
  517. });
  518. it('rejects if replacements is a number', async function () {
  519. await this.sequelize
  520. .query('select :one as foo, :two as bar', { raw: true, replacements: 2 })
  521. .should.be.rejectedWith(
  522. Error,
  523. '"replacements" must be an array or a plain object, but received 2 instead.',
  524. );
  525. });
  526. it('rejects if a replacement is missing', async function () {
  527. await this.sequelize
  528. .query('select :one as foo, :two as bar', { raw: true, replacements: {} })
  529. .should.be.rejectedWith(
  530. Error,
  531. /Named replacement ":\w+" has no entry in the replacement map\./g,
  532. );
  533. });
  534. it('rejects if replacements is a string', async function () {
  535. await this.sequelize
  536. .query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' })
  537. .should.be.rejectedWith(
  538. Error,
  539. '"replacements" must be an array or a plain object, but received "foobar" instead.',
  540. );
  541. });
  542. it('reject if replacements is not a plain object', async function () {
  543. await this.sequelize
  544. .query('select :one as foo, :two as bar', {
  545. raw: true,
  546. replacements: new URL('http://example.com'),
  547. })
  548. .should.be.rejectedWith(
  549. Error,
  550. '"replacements" must be an array or a plain object, but received "http://example.com/" instead.',
  551. );
  552. });
  553. it('reject when binds passed with object and numeric $1 is also present', async function () {
  554. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  555. await this.sequelize
  556. .query(`select $one${typeCast} as foo, $two${typeCast} as bar, $1 as baz`, {
  557. raw: true,
  558. bind: { one: 1, two: 2 },
  559. })
  560. .should.be.rejectedWith(
  561. Error,
  562. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  563. );
  564. });
  565. it('rejects when binds passed as array and a named parameter is also present', async function () {
  566. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  567. await this.sequelize
  568. .query(`select $1${typeCast} as foo, $2${typeCast} as bar, $foo as baz`, {
  569. raw: true,
  570. bind: [1, 2],
  571. })
  572. .should.be.rejectedWith(
  573. Error,
  574. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  575. );
  576. });
  577. it('reject when bind key is $0 and bind is an array', async function () {
  578. await this.sequelize
  579. .query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] })
  580. .should.be.rejectedWith(
  581. Error,
  582. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  583. );
  584. });
  585. it('reject when bind key is $01 and bind is an array', async function () {
  586. await this.sequelize
  587. .query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] })
  588. .should.be.rejectedWith(
  589. Error,
  590. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  591. );
  592. });
  593. it('reject when bind key is missing in the passed array', async function () {
  594. await this.sequelize
  595. .query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] })
  596. .should.be.rejectedWith(
  597. Error,
  598. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  599. );
  600. });
  601. it('reject when bind key is missing in the passed object', async function () {
  602. await this.sequelize
  603. .query('select $one as foo, $two as bar, $three as baz', {
  604. raw: true,
  605. bind: { one: 1, two: 2 },
  606. })
  607. .should.be.rejectedWith(
  608. Error,
  609. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  610. );
  611. });
  612. it('rejects if options.bind is a number', async function () {
  613. await this.sequelize
  614. .query('select $one as foo, $two as bar', { raw: true, bind: 2 })
  615. .should.be.rejectedWith(
  616. Error,
  617. 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
  618. );
  619. });
  620. it('rejects if a bind parameter is not present in options.bind', async function () {
  621. await this.sequelize
  622. .query('select $one as foo, $two as bar', { raw: true, bind: {} })
  623. .should.be.rejectedWith(
  624. Error,
  625. /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
  626. );
  627. });
  628. it('rejects if options.bind is a string', async function () {
  629. await this.sequelize
  630. .query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' })
  631. .should.be.rejectedWith(
  632. Error,
  633. 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
  634. );
  635. });
  636. it('rejects if options.bind is a non-pojo object', async function () {
  637. await this.sequelize
  638. .query('select $one as foo, $two as bar', { raw: true, bind: new Date() })
  639. .should.be.rejectedWith(
  640. Error,
  641. 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
  642. );
  643. });
  644. });
  645. // dialects in which the following values will be returned as bigints instead of ints
  646. const isBigInt = dialectName === 'mysql';
  647. it('dot separated attributes when doing a raw query without nest', async function () {
  648. const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`;
  649. const results = await this.sequelize.query(sql, { raw: true, nest: false });
  650. expect(results[0]).to.deep.equal([{ 'foo.bar.baz': isBigInt ? '1' : 1 }]);
  651. });
  652. it('destructs dot separated attributes when doing a raw query using nest', async function () {
  653. const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`;
  654. const result = await this.sequelize.query(sql, { raw: true, nest: true });
  655. expect(result).to.deep.equal([{ foo: { bar: { baz: isBigInt ? '1' : 1 } } }]);
  656. });
  657. it('replaces token with the passed array', async function () {
  658. const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }];
  659. const result = await this.sequelize.query(
  660. `select ? as ${queryGenerator.quoteIdentifier('foo')}, ? as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  661. { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] },
  662. );
  663. expect(result).to.deep.equal(expected);
  664. });
  665. it('replaces named parameters with the passed object', async function () {
  666. const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }];
  667. await expect(
  668. this.sequelize
  669. .query(
  670. `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  671. { raw: true, replacements: { one: 1, two: 2 } },
  672. )
  673. .then(obj => obj[0]),
  674. ).to.eventually.deep.equal(expected);
  675. });
  676. it('replaces named parameters with the passed object and ignore those which does not qualify', async function () {
  677. const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: '00:00' }];
  678. await expect(
  679. this.sequelize
  680. .query(
  681. `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, '00:00' as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  682. { raw: true, replacements: { one: 1, two: 2 } },
  683. )
  684. .then(obj => obj[0]),
  685. ).to.eventually.deep.equal(expected);
  686. });
  687. it('replaces named parameters with the passed object using the same key twice', async function () {
  688. const expected = [
  689. { foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: isBigInt ? '1' : 1 },
  690. ];
  691. await expect(
  692. this.sequelize
  693. .query(
  694. `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, :one as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  695. { raw: true, replacements: { one: 1, two: 2 } },
  696. )
  697. .then(obj => obj[0]),
  698. ).to.eventually.deep.equal(expected);
  699. });
  700. it('replaces named parameters with the passed object having a null property', async function () {
  701. const expected = [{ foo: isBigInt ? '1' : 1, bar: null }];
  702. await expect(
  703. this.sequelize
  704. .query(
  705. `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  706. { raw: true, replacements: { one: 1, two: null } },
  707. )
  708. .then(obj => obj[0]),
  709. ).to.eventually.deep.equal(expected);
  710. });
  711. // IBM i cannot bind parameter markers for selecting values like in theses
  712. // tests
  713. if (dialectName !== 'ibmi') {
  714. it('binds token with the passed array', async function () {
  715. const expected = [{ foo: 1, bar: 2 }];
  716. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  717. let logSql;
  718. const result = await this.sequelize.query(
  719. `select $1${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $2${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  720. {
  721. type: this.sequelize.QueryTypes.SELECT,
  722. bind: [1, 2],
  723. logging(s) {
  724. logSql = s;
  725. },
  726. },
  727. );
  728. expect(result).to.deep.equal(expected);
  729. if (['postgres', 'sqlite3'].includes(dialectName)) {
  730. expect(logSql).to.include('$1');
  731. }
  732. });
  733. it('binds named parameters with the passed object', async function () {
  734. const expected = [{ foo: 1, bar: 2 }];
  735. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  736. let logSql;
  737. const result = await this.sequelize.query(
  738. `select $one${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $two${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  739. {
  740. raw: true,
  741. bind: { one: 1, two: 2 },
  742. logging(s) {
  743. logSql = s;
  744. },
  745. },
  746. );
  747. expect(result[0]).to.deep.equal(expected);
  748. if (dialectName === 'postgres') {
  749. expect(logSql).to.include('$1');
  750. }
  751. if (dialectName === 'sqlite3') {
  752. expect(logSql).to.include('$one');
  753. }
  754. });
  755. if (dialectName !== 'db2') {
  756. it('binds named parameters with the passed object using the same key twice', async function () {
  757. const typeCast = dialectName === 'postgres' ? '::int' : '';
  758. let logSql;
  759. const result = await this.sequelize.query(
  760. `select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  761. {
  762. raw: true,
  763. bind: { one: 1, two: 2 },
  764. logging(s) {
  765. logSql = s;
  766. },
  767. },
  768. );
  769. if (dialectName === 'ibmi') {
  770. expect(result[0]).to.deep.equal([{ FOO: 1, BAR: 2, BAZ: 1 }]);
  771. } else {
  772. expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]);
  773. }
  774. if (dialectName === 'postgres') {
  775. expect(logSql).to.include('$1');
  776. expect(logSql).to.include('$2');
  777. expect(logSql).to.not.include('$3');
  778. }
  779. });
  780. }
  781. it('binds named parameters with the passed object having a null property', async function () {
  782. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  783. const result = await this.sequelize.query(
  784. `select $one${typeCast} as foo, $two${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  785. { raw: true, bind: { one: 1, two: null } },
  786. );
  787. const expected = ['db2', 'ibmi'].includes(dialectName)
  788. ? [{ FOO: 1, BAR: null }]
  789. : [{ foo: 1, bar: null }];
  790. expect(result[0]).to.deep.equal(expected);
  791. });
  792. // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed.
  793. it('does not transform $$ in strings (positional)', async function () {
  794. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  795. let logSql;
  796. const result = await this.sequelize.query(
  797. `select $1${typeCast} as foo, '$$ / $$1' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  798. {
  799. raw: true,
  800. bind: [1],
  801. logging(s) {
  802. logSql = s;
  803. },
  804. },
  805. );
  806. const expected = ['db2', 'ibmi'].includes(dialectName)
  807. ? [{ FOO: 1, BAR: '$$ / $$1' }]
  808. : [{ foo: 1, bar: '$$ / $$1' }];
  809. expect(result[0]).to.deep.equal(expected);
  810. if (['postgres', 'sqlite3', 'db2', 'ibmi'].includes(dialectName)) {
  811. expect(logSql).to.include('$1');
  812. }
  813. });
  814. // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed.
  815. it('does not transform $$ in strings (named)', async function () {
  816. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  817. const result = await this.sequelize.query(
  818. `select $one${typeCast} as foo, '$$ / $$one' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  819. { raw: true, bind: { one: 1 } },
  820. );
  821. const expected = ['db2', 'ibmi'].includes(dialectName)
  822. ? [{ FOO: 1, BAR: '$$ / $$one' }]
  823. : [{ foo: 1, bar: '$$ / $$one' }];
  824. expect(result[0]).to.deep.equal(expected);
  825. });
  826. it(`does not treat a $ as a bind param if it's in the middle of an identifier`, async function () {
  827. const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
  828. const result = await this.sequelize.query(
  829. `select $one${typeCast} as foo$bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  830. { raw: true, bind: { one: 1 } },
  831. );
  832. const expected = ['db2', 'ibmi'].includes(dialectName)
  833. ? [{ FOO$BAR: 1 }]
  834. : [{ foo$bar: 1 }];
  835. expect(result[0]).to.deep.equal(expected);
  836. });
  837. }
  838. if (['postgres', 'sqlite3', 'mssql'].includes(dialectName)) {
  839. it('does not improperly escape arrays of strings bound to named parameters', async function () {
  840. const result = await this.sequelize.query('select :stringArray as foo', {
  841. raw: true,
  842. replacements: { stringArray: sql.list(['"string"']) },
  843. });
  844. expect(result[0]).to.deep.equal([{ foo: '"string"' }]);
  845. });
  846. }
  847. it('handles AS in conjunction with functions just fine', async function () {
  848. let datetime = dialectName === 'sqlite3' ? "date('now')" : 'NOW()';
  849. if (dialectName === 'mssql') {
  850. datetime = 'GETDATE()';
  851. }
  852. const [result] = await this.sequelize.query(
  853. `SELECT ${datetime} AS t${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
  854. );
  855. expect(dayjs(result[0].t).isValid()).to.be.true;
  856. });
  857. if (getTestDialect() === 'postgres') {
  858. it('replaces named parameters with the passed object and ignores casts', async function () {
  859. await expect(
  860. this.sequelize
  861. .query("select :one as foo, :two as bar, '1000'::integer as baz", {
  862. raw: true,
  863. replacements: { one: 1, two: 2 },
  864. })
  865. .then(obj => obj[0]),
  866. ).to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]);
  867. });
  868. it('supports WITH queries', async function () {
  869. await expect(
  870. this.sequelize
  871. .query(
  872. 'WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t',
  873. )
  874. .then(obj => obj[0]),
  875. ).to.eventually.deep.equal([{ sum: '5050' }]);
  876. });
  877. }
  878. });
  879. });