123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- 'use strict';
- const { expect } = require('chai');
- const {
- DatabaseError,
- DataTypes,
- ForeignKeyConstraintError,
- Sequelize,
- sql,
- UniqueConstraintError,
- } = require('@sequelize/core');
- const sinon = require('sinon');
- const dayjs = require('dayjs');
- const {
- allowDeprecationsInSuite,
- beforeEach2,
- createSequelizeInstance,
- createSingleTestSequelizeInstance,
- destroySequelizeAfterTest,
- getTestDialect,
- getTestDialectTeaser,
- sequelize,
- } = require('../support');
- const dialectName = getTestDialect();
- const queryGenerator = sequelize.queryGenerator;
- const qq = str => {
- if (['postgres', 'mssql', 'db2', 'ibmi'].includes(dialectName)) {
- return `"${str}"`;
- }
- if (['mysql', 'mariadb', 'sqlite3'].includes(dialectName)) {
- return `\`${str}\``;
- }
- return str;
- };
- describe(getTestDialectTeaser('Sequelize'), () => {
- allowDeprecationsInSuite(['SEQUELIZE0023']);
- describe('query', () => {
- afterEach(() => {
- console.log.restore && console.log.restore();
- });
- beforeEach(async function () {
- this.User = this.sequelize.define('User', {
- username: {
- type: DataTypes.STRING,
- unique: true,
- },
- emailAddress: {
- type: DataTypes.STRING,
- field: 'email_address',
- },
- });
- this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${qq(
- 'createdAt',
- )}, ${qq(
- 'updatedAt',
- )}) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- if (['db2', 'ibmi'].includes(dialectName)) {
- this.insertQuery = `INSERT INTO ${qq(this.User.tableName)}
- ("username", "email_address", ${qq('createdAt')}, ${qq('updatedAt')}) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- }
- await this.User.sync({ force: true });
- });
- it('executes a query the internal way', async function () {
- await this.sequelize.query(this.insertQuery, { raw: true });
- });
- it('executes a query if only the sql is passed', async function () {
- await this.sequelize.query(this.insertQuery);
- });
- describe('QueryTypes', () => {
- it('RAW', async function () {
- await this.sequelize.query(this.insertQuery, {
- type: Sequelize.QueryTypes.RAW,
- });
- const [rows, count] = await this.sequelize.query(
- `SELECT * FROM ${qq(this.User.tableName)};`,
- {
- type: Sequelize.QueryTypes.RAW,
- },
- );
- expect(rows).to.be.an.instanceof(Array);
- expect(count).to.be.ok;
- });
- });
- describe('retry', () => {
- it('properly bind parameters on extra retries', async function () {
- const payload = {
- username: 'test',
- createdAt: '2010-10-10 00:00:00',
- updatedAt: '2010-10-10 00:00:00',
- };
- const spy = sinon.spy();
- await this.User.create(payload);
- await expect(
- this.sequelize.query(
- `
- INSERT INTO ${qq(this.User.tableName)} (${qq('username')},${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt);
- `,
- {
- bind: payload,
- logging: spy,
- retry: {
- max: 3,
- match: [/Validation/],
- },
- },
- ),
- ).to.be.rejectedWith(Sequelize.UniqueConstraintError);
- expect(spy.callCount).to.eql(['db2', 'ibmi'].includes(dialectName) ? 1 : 3);
- });
- });
- describe('logging', () => {
- it('executes a query with global benchmarking option and custom logger', async () => {
- const logger = sinon.spy();
- const sequelize = createSingleTestSequelizeInstance({
- logging: logger,
- benchmark: true,
- });
- await sequelize.query(`select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`);
- expect(logger.calledOnce).to.be.true;
- expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/);
- expect(typeof logger.args[0][1] === 'number').to.be.true;
- });
- it('executes a query with benchmarking option and custom logger', async function () {
- const logger = sinon.spy();
- await this.sequelize.query(
- `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
- {
- logging: logger,
- benchmark: true,
- },
- );
- expect(logger.calledOnce).to.be.true;
- expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/);
- expect(typeof logger.args[0][1] === 'number').to.be.true;
- });
- it('executes a query with queryLabel option and custom logger', async () => {
- const logger = sinon.spy();
- const sequelize = createSingleTestSequelizeInstance({
- logging: logger,
- });
- await sequelize.query(
- `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
- {
- queryLabel: 'tricky select',
- },
- );
- expect(logger.calledOnce).to.be.true;
- expect(logger.args[0][0]).to.be.match(
- /^tricky select[\n]Executing \((\d*|default)\): select 1/,
- );
- });
- it('executes a query with empty string, queryLabel option and custom logger', async () => {
- const logger = sinon.spy();
- const sequelize = createSingleTestSequelizeInstance({
- logging: logger,
- });
- await sequelize.query(
- `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
- {
- queryLabel: '',
- },
- );
- expect(logger.calledOnce).to.be.true;
- expect(logger.args[0][0]).to.be.match(/^Executing \((\d*|default)\): select 1/);
- });
- it('executes a query with benchmarking option, queryLabel option and custom logger', async () => {
- const logger = sinon.spy();
- const sequelize = createSingleTestSequelizeInstance({
- logging: logger,
- benchmark: true,
- });
- await sequelize.query(
- `select 1${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''};`,
- {
- queryLabel: 'tricky select',
- },
- );
- expect(logger.calledOnce).to.be.true;
- expect(logger.args[0][0]).to.be.match(
- /^tricky select[\n]Executed \((\d*|default)\): select 1/,
- );
- });
- describe('with logQueryParameters', () => {
- const vars = beforeEach2(async () => {
- const sequelize = createSequelizeInstance({
- benchmark: true,
- logQueryParameters: true,
- });
- const User = sequelize.define(
- 'User',
- {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- username: {
- type: DataTypes.STRING,
- },
- emailAddress: {
- type: DataTypes.STRING,
- },
- },
- {
- timestamps: false,
- },
- );
- await User.sync({ force: true });
- return { sequelize, User };
- });
- afterEach(() => {
- return vars.sequelize.close();
- });
- it('add parameters in log sql', async () => {
- let createSql;
- let updateSql;
- const user = await vars.User.create(
- {
- username: 'john',
- emailAddress: 'john@gmail.com',
- },
- {
- logging: s => {
- createSql = s;
- },
- },
- );
- user.username = 'li';
- await user.save({
- logging: s => {
- updateSql = s;
- },
- });
- if (
- dialectName === 'db2' ||
- dialectName === 'postgres' ||
- dialectName === 'mariadb' ||
- dialectName === 'mysql'
- ) {
- // these dialects use positional bind parameters
- expect(createSql.endsWith(` with parameters [ 'john', 'john@gmail.com' ]`)).to.eq(
- true,
- 'bind parameters incorrectly logged for INSERT query',
- );
- expect(updateSql.endsWith(` with parameters [ 'li', 1 ]`)).to.eq(
- true,
- 'bind parameters incorrectly logged for UPDATE query',
- );
- } else {
- // these dialects use named bind parameters
- expect(
- createSql.endsWith(
- ` with parameters { sequelize_1: 'john', sequelize_2: 'john@gmail.com' }`,
- ),
- ).to.eq(true, 'bind parameters incorrectly logged for INSERT query');
- expect(
- updateSql.endsWith(` with parameters { sequelize_1: 'li', sequelize_2: 1 }`),
- ).to.eq(true, 'bind parameters incorrectly logged for UPDATE query');
- }
- });
- it('add parameters in log sql when use bind value', async () => {
- let logSql;
- let typeCast = dialectName === 'postgres' ? '::text' : '';
- if (['db2'].includes(dialectName)) {
- typeCast = '::VARCHAR';
- }
- await vars.sequelize.query(
- `select $1${typeCast} as foo, $2${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- {
- bind: ['foo', 'bar'],
- logging: s => {
- logSql = s;
- },
- },
- );
- expect(logSql.endsWith(` with parameters [ 'foo', 'bar' ]`)).to.eq(
- true,
- 'bind parameters incorrectly logged.',
- );
- });
- });
- });
- it('executes select queries correctly', async function () {
- await this.sequelize.query(this.insertQuery);
- const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`);
- expect(
- users.map(u => {
- return u.username;
- }),
- ).to.include('john');
- });
- it('executes select queries correctly when quoteIdentifiers is false', async function () {
- const sequelize = createSequelizeInstance({
- quoteIdentifiers: false,
- });
- destroySequelizeAfterTest(sequelize);
- await sequelize.query(this.insertQuery);
- const [users] = await sequelize.query(`select * from ${qq(this.User.tableName)}`);
- expect(
- users.map(u => {
- return u.username;
- }),
- ).to.include('john');
- });
- it('executes select query with dot notation results', async function () {
- await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`);
- await this.sequelize.query(this.insertQuery);
- const [users] = await this.sequelize.query(
- `select ${qq('username')} as ${qq('user.username')} from ${qq(this.User.tableName)}`,
- );
- expect(users).to.deep.equal([{ 'user.username': 'john' }]);
- });
- it('executes select query with dot notation results and nest it', async function () {
- await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`);
- await this.sequelize.query(this.insertQuery);
- const users = await this.sequelize.query(
- `select ${qq('username')} as ${qq('user.username')} from ${qq(this.User.tableName)}`,
- { raw: true, nest: true },
- );
- expect(
- users.map(u => {
- return u.user;
- }),
- ).to.deep.equal([{ username: 'john' }]);
- });
- if (dialectName === 'mysql') {
- it('executes stored procedures', async function () {
- await this.sequelize.query(this.insertQuery);
- await this.sequelize.query('DROP PROCEDURE IF EXISTS foo');
- await this.sequelize.query(`CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};`);
- const users = await this.sequelize.query('CALL foo()');
- expect(
- users.map(u => {
- return u.username;
- }),
- ).to.include('john');
- });
- } else if (dialectName === 'db2') {
- it('executes stored procedures', async function () {
- const { sequelize } = this;
- await sequelize.query(this.insertQuery);
- try {
- await sequelize.query('DROP PROCEDURE foo');
- } catch (error) {
- // DB2 does not support DROP PROCEDURE IF EXISTS
- // -204 means "FOO" does not exist
- // https://www.ibm.com/docs/en/db2-for-zos/11?topic=sec-204
- if (error.cause.sqlcode !== -204) {
- throw error;
- }
- }
- await sequelize.query(
- `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`,
- );
- const users = await sequelize.query('CALL foo()');
- expect(users.map(u => u.username)).to.include('john');
- });
- }
- it('uses the passed model', async function () {
- await this.sequelize.query(this.insertQuery);
- const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
- model: this.User,
- });
- expect(users[0]).to.be.instanceof(this.User);
- });
- it('maps the field names to attributes based on the passed model', async function () {
- await this.sequelize.query(this.insertQuery);
- const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
- model: this.User,
- mapToModel: true,
- });
- expect(users[0].emailAddress).to.equal('john@gmail.com');
- });
- it('arbitrarily map the field names', async function () {
- await this.sequelize.query(this.insertQuery);
- const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
- type: 'SELECT',
- fieldMap: { username: 'userName', email_address: 'email' },
- });
- expect(users[0].userName).to.equal('john');
- expect(users[0].email).to.equal('john@gmail.com');
- });
- it('keeps field names that are mapped to the same name', async function () {
- await this.sequelize.query(this.insertQuery);
- const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, {
- type: 'SELECT',
- fieldMap: { username: 'username', email_address: 'email' },
- });
- expect(users[0].username).to.equal('john');
- expect(users[0].email).to.equal('john@gmail.com');
- });
- // Only run stacktrace tests on Node 12+, since only Node 12+ supports
- // async stacktraces
- const nodeVersionMatch = process.version.match(/^v(\d+)/);
- let nodeMajorVersion = 0;
- if (nodeVersionMatch && nodeVersionMatch[1]) {
- nodeMajorVersion = Number.parseInt(nodeVersionMatch[1], 10);
- }
- if (nodeMajorVersion >= 12) {
- describe('stacktraces', () => {
- beforeEach(async function () {
- this.UserVisit = this.sequelize.define(
- 'UserVisit',
- {
- userId: {
- type: DataTypes.STRING,
- field: 'user_id',
- },
- visitedAt: {
- type: DataTypes.DATE,
- field: 'visited_at',
- },
- },
- {
- indexes: [{ name: 'user_id', fields: ['user_id'] }],
- },
- );
- this.User.hasMany(this.UserVisit, { foreignKey: 'user_id' });
- await this.UserVisit.sync({ force: true });
- });
- it('emits raw errors if requested', async function () {
- const sql = 'SELECT 1 FROM NotFoundTable';
- await expect(
- this.sequelize.query(sql, { rawErrors: false }),
- ).to.eventually.be.rejectedWith(DatabaseError);
- await expect(
- this.sequelize.query(sql, { rawErrors: true }),
- ).to.eventually.be.rejected.and.not.be.an.instanceOf(DatabaseError);
- });
- it('emits full stacktraces for generic database error', async function () {
- let error = null;
- try {
- await this.sequelize.query(
- `select * from ${qq(this.User.tableName)} where ${qq('unknown_column')} = 1`,
- );
- } catch (error_) {
- error = error_;
- }
- expect(error).to.be.instanceOf(DatabaseError);
- expect(error.stack).to.contain('query.test');
- });
- it('emits full stacktraces for unique constraint error', async function () {
- let query;
- if (['db2', 'ibmi'].includes(dialectName)) {
- query = `INSERT INTO ${qq(this.User.tableName)} ("username", "email_address", ${qq(
- 'createdAt',
- )}, ${qq(
- 'updatedAt',
- )}) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- } else {
- query = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${qq(
- 'createdAt',
- )}, ${qq(
- 'updatedAt',
- )}) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- }
- let error = null;
- try {
- // Insert 1 row
- await this.sequelize.query(query);
- // Try inserting a duplicate row
- await this.sequelize.query(query);
- } catch (error_) {
- error = error_;
- }
- expect(error).to.be.instanceOf(UniqueConstraintError);
- expect(error.stack).to.contain('query.test');
- });
- it('emits full stacktraces for constraint validation error', async function () {
- let error = null;
- try {
- let query;
- if (['db2', 'ibmi'].includes(dialectName)) {
- query = `INSERT INTO ${qq(this.UserVisit.tableName)} ("user_id", "visited_at", ${qq(
- 'createdAt',
- )}, ${qq(
- 'updatedAt',
- )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- } else {
- query = `INSERT INTO ${qq(this.UserVisit.tableName)} (user_id, visited_at, ${qq(
- 'createdAt',
- )}, ${qq(
- 'updatedAt',
- )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`;
- }
- await this.sequelize.query(query);
- } catch (error_) {
- error = error_;
- }
- expect(error).to.be.instanceOf(ForeignKeyConstraintError);
- expect(error.stack).to.contain('query.test');
- });
- });
- }
- describe('rejections', () => {
- it('reject if the query is not a string', async function () {
- // this is a legacy, removed signature
- await this.sequelize
- .query(
- { query: 'select ? as foo, ? as bar', values: [1, 2] },
- { raw: true, replacements: [1, 2] },
- )
- .should.be.rejectedWith(
- Error,
- '"sql" cannot be an object. Pass a string instead, and pass bind and replacement parameters through the "options" parameter',
- );
- });
- it('reject when key is missing in the passed object', async function () {
- await this.sequelize
- .query('select :one as foo, :two as bar, :three as baz', {
- raw: true,
- replacements: { one: 1, two: 2 },
- })
- .should.be.rejectedWith(
- Error,
- /Named replacement ":\w+" has no entry in the replacement map\./g,
- );
- });
- it('rejects if replacements is a number', async function () {
- await this.sequelize
- .query('select :one as foo, :two as bar', { raw: true, replacements: 2 })
- .should.be.rejectedWith(
- Error,
- '"replacements" must be an array or a plain object, but received 2 instead.',
- );
- });
- it('rejects if a replacement is missing', async function () {
- await this.sequelize
- .query('select :one as foo, :two as bar', { raw: true, replacements: {} })
- .should.be.rejectedWith(
- Error,
- /Named replacement ":\w+" has no entry in the replacement map\./g,
- );
- });
- it('rejects if replacements is a string', async function () {
- await this.sequelize
- .query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' })
- .should.be.rejectedWith(
- Error,
- '"replacements" must be an array or a plain object, but received "foobar" instead.',
- );
- });
- it('reject if replacements is not a plain object', async function () {
- await this.sequelize
- .query('select :one as foo, :two as bar', {
- raw: true,
- replacements: new URL('http://example.com'),
- })
- .should.be.rejectedWith(
- Error,
- '"replacements" must be an array or a plain object, but received "http://example.com/" instead.',
- );
- });
- it('reject when binds passed with object and numeric $1 is also present', async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- await this.sequelize
- .query(`select $one${typeCast} as foo, $two${typeCast} as bar, $1 as baz`, {
- raw: true,
- bind: { one: 1, two: 2 },
- })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('rejects when binds passed as array and a named parameter is also present', async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- await this.sequelize
- .query(`select $1${typeCast} as foo, $2${typeCast} as bar, $foo as baz`, {
- raw: true,
- bind: [1, 2],
- })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('reject when bind key is $0 and bind is an array', async function () {
- await this.sequelize
- .query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('reject when bind key is $01 and bind is an array', async function () {
- await this.sequelize
- .query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('reject when bind key is missing in the passed array', async function () {
- await this.sequelize
- .query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('reject when bind key is missing in the passed object', async function () {
- await this.sequelize
- .query('select $one as foo, $two as bar, $three as baz', {
- raw: true,
- bind: { one: 1, two: 2 },
- })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('rejects if options.bind is a number', async function () {
- await this.sequelize
- .query('select $one as foo, $two as bar', { raw: true, bind: 2 })
- .should.be.rejectedWith(
- Error,
- 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
- );
- });
- it('rejects if a bind parameter is not present in options.bind', async function () {
- await this.sequelize
- .query('select $one as foo, $two as bar', { raw: true, bind: {} })
- .should.be.rejectedWith(
- Error,
- /Query includes bind parameter "\$\w+", but no value has been provided for that bind parameter\./g,
- );
- });
- it('rejects if options.bind is a string', async function () {
- await this.sequelize
- .query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' })
- .should.be.rejectedWith(
- Error,
- 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
- );
- });
- it('rejects if options.bind is a non-pojo object', async function () {
- await this.sequelize
- .query('select $one as foo, $two as bar', { raw: true, bind: new Date() })
- .should.be.rejectedWith(
- Error,
- 'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',
- );
- });
- });
- // dialects in which the following values will be returned as bigints instead of ints
- const isBigInt = dialectName === 'mysql';
- it('dot separated attributes when doing a raw query without nest', async function () {
- const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`;
- const results = await this.sequelize.query(sql, { raw: true, nest: false });
- expect(results[0]).to.deep.equal([{ 'foo.bar.baz': isBigInt ? '1' : 1 }]);
- });
- it('destructs dot separated attributes when doing a raw query using nest', async function () {
- const sql = `select 1 as ${queryGenerator.quoteIdentifier('foo.bar.baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`;
- const result = await this.sequelize.query(sql, { raw: true, nest: true });
- expect(result).to.deep.equal([{ foo: { bar: { baz: isBigInt ? '1' : 1 } } }]);
- });
- it('replaces token with the passed array', async function () {
- const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }];
- const result = await this.sequelize.query(
- `select ? as ${queryGenerator.quoteIdentifier('foo')}, ? as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] },
- );
- expect(result).to.deep.equal(expected);
- });
- it('replaces named parameters with the passed object', async function () {
- const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2 }];
- await expect(
- this.sequelize
- .query(
- `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, replacements: { one: 1, two: 2 } },
- )
- .then(obj => obj[0]),
- ).to.eventually.deep.equal(expected);
- });
- it('replaces named parameters with the passed object and ignore those which does not qualify', async function () {
- const expected = [{ foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: '00:00' }];
- await expect(
- this.sequelize
- .query(
- `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, '00:00' as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, replacements: { one: 1, two: 2 } },
- )
- .then(obj => obj[0]),
- ).to.eventually.deep.equal(expected);
- });
- it('replaces named parameters with the passed object using the same key twice', async function () {
- const expected = [
- { foo: isBigInt ? '1' : 1, bar: isBigInt ? '2' : 2, baz: isBigInt ? '1' : 1 },
- ];
- await expect(
- this.sequelize
- .query(
- `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}, :one as ${queryGenerator.quoteIdentifier('baz')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, replacements: { one: 1, two: 2 } },
- )
- .then(obj => obj[0]),
- ).to.eventually.deep.equal(expected);
- });
- it('replaces named parameters with the passed object having a null property', async function () {
- const expected = [{ foo: isBigInt ? '1' : 1, bar: null }];
- await expect(
- this.sequelize
- .query(
- `select :one as ${queryGenerator.quoteIdentifier('foo')}, :two as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, replacements: { one: 1, two: null } },
- )
- .then(obj => obj[0]),
- ).to.eventually.deep.equal(expected);
- });
- // IBM i cannot bind parameter markers for selecting values like in theses
- // tests
- if (dialectName !== 'ibmi') {
- it('binds token with the passed array', async function () {
- const expected = [{ foo: 1, bar: 2 }];
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- let logSql;
- const result = await this.sequelize.query(
- `select $1${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $2${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- {
- type: this.sequelize.QueryTypes.SELECT,
- bind: [1, 2],
- logging(s) {
- logSql = s;
- },
- },
- );
- expect(result).to.deep.equal(expected);
- if (['postgres', 'sqlite3'].includes(dialectName)) {
- expect(logSql).to.include('$1');
- }
- });
- it('binds named parameters with the passed object', async function () {
- const expected = [{ foo: 1, bar: 2 }];
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- let logSql;
- const result = await this.sequelize.query(
- `select $one${typeCast} as ${queryGenerator.quoteIdentifier('foo')}, $two${typeCast} as ${queryGenerator.quoteIdentifier('bar')}${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- {
- raw: true,
- bind: { one: 1, two: 2 },
- logging(s) {
- logSql = s;
- },
- },
- );
- expect(result[0]).to.deep.equal(expected);
- if (dialectName === 'postgres') {
- expect(logSql).to.include('$1');
- }
- if (dialectName === 'sqlite3') {
- expect(logSql).to.include('$one');
- }
- });
- if (dialectName !== 'db2') {
- it('binds named parameters with the passed object using the same key twice', async function () {
- const typeCast = dialectName === 'postgres' ? '::int' : '';
- let logSql;
- const result = await this.sequelize.query(
- `select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- {
- raw: true,
- bind: { one: 1, two: 2 },
- logging(s) {
- logSql = s;
- },
- },
- );
- if (dialectName === 'ibmi') {
- expect(result[0]).to.deep.equal([{ FOO: 1, BAR: 2, BAZ: 1 }]);
- } else {
- expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]);
- }
- if (dialectName === 'postgres') {
- expect(logSql).to.include('$1');
- expect(logSql).to.include('$2');
- expect(logSql).to.not.include('$3');
- }
- });
- }
- it('binds named parameters with the passed object having a null property', async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- const result = await this.sequelize.query(
- `select $one${typeCast} as foo, $two${typeCast} as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, bind: { one: 1, two: null } },
- );
- const expected = ['db2', 'ibmi'].includes(dialectName)
- ? [{ FOO: 1, BAR: null }]
- : [{ foo: 1, bar: null }];
- expect(result[0]).to.deep.equal(expected);
- });
- // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed.
- it('does not transform $$ in strings (positional)', async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- let logSql;
- const result = await this.sequelize.query(
- `select $1${typeCast} as foo, '$$ / $$1' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- {
- raw: true,
- bind: [1],
- logging(s) {
- logSql = s;
- },
- },
- );
- const expected = ['db2', 'ibmi'].includes(dialectName)
- ? [{ FOO: 1, BAR: '$$ / $$1' }]
- : [{ foo: 1, bar: '$$ / $$1' }];
- expect(result[0]).to.deep.equal(expected);
- if (['postgres', 'sqlite3', 'db2', 'ibmi'].includes(dialectName)) {
- expect(logSql).to.include('$1');
- }
- });
- // this was a legacy band aid that has since been removed, because the underlying issue (transforming bind params in strings) has been fixed.
- it('does not transform $$ in strings (named)', async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- const result = await this.sequelize.query(
- `select $one${typeCast} as foo, '$$ / $$one' as bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, bind: { one: 1 } },
- );
- const expected = ['db2', 'ibmi'].includes(dialectName)
- ? [{ FOO: 1, BAR: '$$ / $$one' }]
- : [{ foo: 1, bar: '$$ / $$one' }];
- expect(result[0]).to.deep.equal(expected);
- });
- it(`does not treat a $ as a bind param if it's in the middle of an identifier`, async function () {
- const typeCast = ['postgres', 'db2'].includes(dialectName) ? '::int' : '';
- const result = await this.sequelize.query(
- `select $one${typeCast} as foo$bar${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- { raw: true, bind: { one: 1 } },
- );
- const expected = ['db2', 'ibmi'].includes(dialectName)
- ? [{ FOO$BAR: 1 }]
- : [{ foo$bar: 1 }];
- expect(result[0]).to.deep.equal(expected);
- });
- }
- if (['postgres', 'sqlite3', 'mssql'].includes(dialectName)) {
- it('does not improperly escape arrays of strings bound to named parameters', async function () {
- const result = await this.sequelize.query('select :stringArray as foo', {
- raw: true,
- replacements: { stringArray: sql.list(['"string"']) },
- });
- expect(result[0]).to.deep.equal([{ foo: '"string"' }]);
- });
- }
- it('handles AS in conjunction with functions just fine', async function () {
- let datetime = dialectName === 'sqlite3' ? "date('now')" : 'NOW()';
- if (dialectName === 'mssql') {
- datetime = 'GETDATE()';
- }
- const [result] = await this.sequelize.query(
- `SELECT ${datetime} AS t${dialectName === 'ibmi' ? ' FROM SYSIBM.SYSDUMMY1' : ''}`,
- );
- expect(dayjs(result[0].t).isValid()).to.be.true;
- });
- if (getTestDialect() === 'postgres') {
- it('replaces named parameters with the passed object and ignores casts', async function () {
- await expect(
- this.sequelize
- .query("select :one as foo, :two as bar, '1000'::integer as baz", {
- raw: true,
- replacements: { one: 1, two: 2 },
- })
- .then(obj => obj[0]),
- ).to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]);
- });
- it('supports WITH queries', async function () {
- await expect(
- this.sequelize
- .query(
- 'WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t',
- )
- .then(obj => obj[0]),
- ).to.eventually.deep.equal([{ sum: '5050' }]);
- });
- }
- });
- });
|