123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import { DataTypes, literal } from '@sequelize/core';
- import { expect } from 'chai';
- import sinon from 'sinon';
- import { beforeAll2, expectsql, sequelize } from '../../support';
- const dialectName = sequelize.dialect.name;
- describe('QueryInterface#upsert', () => {
- if (!sequelize.dialect.supports.upserts) {
- return;
- }
- const vars = beforeAll2(() => {
- const User = sequelize.define(
- 'User',
- {
- firstName: DataTypes.STRING,
- },
- { timestamps: false },
- );
- return { User };
- });
- afterEach(() => {
- sinon.restore();
- });
- // you'll find more replacement tests in query-generator tests
- it('does not parse replacements outside of raw sql', async () => {
- const { User } = vars;
- const stub = sinon.stub(sequelize, 'queryRaw');
- await sequelize.queryInterface.upsert(
- User.tableName,
- { firstName: ':name' },
- { firstName: ':name' },
- { id: ':id' },
- {
- model: User,
- replacements: {
- name: 'Zoe',
- data: 'abc',
- },
- },
- );
- expect(stub.callCount).to.eq(1);
- const firstCall = stub.getCall(0);
- expectsql(firstCall.args[0], {
- default:
- 'INSERT INTO [Users] ([firstName]) VALUES ($sequelize_1) ON CONFLICT ([id]) DO UPDATE SET [firstName]=EXCLUDED.[firstName];',
- 'mariadb mysql':
- 'INSERT INTO `Users` (`firstName`) VALUES ($sequelize_1) ON DUPLICATE KEY UPDATE `firstName`=$sequelize_1;',
- mssql: `
- MERGE INTO [Users] WITH(HOLDLOCK)
- AS [Users_target]
- USING (VALUES(N':name')) AS [Users_source]([firstName])
- ON [Users_target].[id] = [Users_source].[id]
- WHEN MATCHED THEN
- UPDATE SET [Users_target].[firstName] = N':name'
- WHEN NOT MATCHED THEN
- INSERT ([firstName]) VALUES(N':name') OUTPUT $action, INSERTED.*;
- `,
- db2: `
- MERGE INTO "Users"
- AS "Users_target"
- USING (VALUES(':name')) AS "Users_source"("firstName")
- ON "Users_target"."id" = "Users_source"."id"
- WHEN MATCHED THEN
- UPDATE SET "Users_target"."firstName" = ':name'
- WHEN NOT MATCHED THEN
- INSERT ("firstName") VALUES(':name');
- `,
- });
- if (dialectName === 'mssql' || dialectName === 'db2') {
- expect(firstCall.args[1]?.bind).to.be.undefined;
- } else {
- expect(firstCall.args[1]?.bind).to.deep.eq({
- sequelize_1: ':name',
- });
- }
- });
- it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => {
- const { User } = vars;
- sinon.stub(sequelize, 'queryRaw');
- await expect(
- sequelize.queryInterface.upsert(
- User.tableName,
- { firstName: literal('$sequelize_test') },
- { firstName: ':name' },
- { id: ':id' },
- {
- model: User,
- bind: {
- sequelize_test: 'test',
- },
- },
- ),
- ).to.be.rejectedWith(
- 'Bind parameters cannot start with "sequelize_", these bind parameters are reserved by Sequelize.',
- );
- });
- it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => {
- const { User } = vars;
- const stub = sinon.stub(sequelize, 'queryRaw');
- await sequelize.queryInterface.upsert(
- User.tableName,
- {
- firstName: literal('$firstName'),
- lastName: 'Doe',
- },
- {},
- // TODO: weird mssql/db2 specific behavior that should be unified
- dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {},
- {
- model: User,
- bind: {
- firstName: 'John',
- },
- },
- );
- expect(stub.callCount).to.eq(1);
- const firstCall = stub.getCall(0);
- expectsql(firstCall.args[0], {
- default:
- 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($firstName,$sequelize_1) ON CONFLICT ([id]) DO NOTHING;',
- 'mariadb mysql':
- 'INSERT INTO `Users` (`firstName`,`lastName`) VALUES ($firstName,$sequelize_1) ON DUPLICATE KEY UPDATE `id`=`id`;',
- mssql: `
- MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target]
- USING (VALUES($firstName, N'Doe')) AS [Users_source]([firstName], [lastName])
- ON [Users_target].[id] = [Users_source].[id]
- WHEN NOT MATCHED THEN
- INSERT ([firstName], [lastName]) VALUES($firstName, N'Doe')
- OUTPUT $action, INSERTED.*;
- `,
- db2: `
- MERGE INTO "Users" AS "Users_target"
- USING (VALUES($firstName, 'Doe')) AS "Users_source"("firstName", "lastName")
- ON "Users_target"."id" = "Users_source"."id"
- WHEN NOT MATCHED THEN
- INSERT ("firstName", "lastName") VALUES($firstName, 'Doe');
- `,
- });
- if (dialectName === 'mssql' || dialectName === 'db2') {
- expect(firstCall.args[1]?.bind).to.deep.eq({
- firstName: 'John',
- });
- } else {
- expect(firstCall.args[1]?.bind).to.deep.eq({
- firstName: 'John',
- sequelize_1: 'Doe',
- });
- }
- });
- it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => {
- const { User } = vars;
- const stub = sinon.stub(sequelize, 'queryRaw');
- await sequelize.queryInterface.upsert(
- User.tableName,
- {
- firstName: literal('$1'),
- lastName: 'Doe',
- },
- {},
- // TODO: weird mssql/db2 specific behavior that should be unified
- dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {},
- {
- model: User,
- bind: ['John'],
- },
- );
- expect(stub.callCount).to.eq(1);
- const firstCall = stub.getCall(0);
- expectsql(firstCall.args[0], {
- default:
- 'INSERT INTO [Users] ([firstName],[lastName]) VALUES ($1,$sequelize_1) ON CONFLICT ([id]) DO NOTHING;',
- 'mariadb mysql':
- 'INSERT INTO `Users` (`firstName`,`lastName`) VALUES ($1,$sequelize_1) ON DUPLICATE KEY UPDATE `id`=`id`;',
- mssql: `
- MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target]
- USING (VALUES($1, N'Doe')) AS [Users_source]([firstName], [lastName])
- ON [Users_target].[id] = [Users_source].[id]
- WHEN NOT MATCHED THEN
- INSERT ([firstName], [lastName]) VALUES($1, N'Doe')
- OUTPUT $action, INSERTED.*;
- `,
- db2: `
- MERGE INTO "Users" AS "Users_target"
- USING (VALUES($1, 'Doe')) AS "Users_source"("firstName", "lastName")
- ON "Users_target"."id" = "Users_source"."id"
- WHEN NOT MATCHED THEN
- INSERT ("firstName", "lastName") VALUES($1, 'Doe');
- `,
- });
- // mssql does not generate any bind parameter
- if (dialectName === 'mssql' || dialectName === 'db2') {
- expect(firstCall.args[1]?.bind).to.deep.eq(['John']);
- } else {
- expect(firstCall.args[1]?.bind).to.deep.eq({
- 1: 'John',
- sequelize_1: 'Doe',
- });
- }
- });
- it('binds parameters if they are literals', async () => {
- const { User } = vars;
- const stub = sinon.stub(sequelize, 'queryRaw');
- await sequelize.queryInterface.upsert(
- User.tableName,
- {
- firstName: 'Jonh',
- counter: literal('`counter` + 1'),
- },
- {
- counter: literal('`counter` + 1'),
- },
- // TODO: weird mssql/db2 specific behavior that should be unified
- dialectName === 'mssql' || dialectName === 'db2' ? { id: 1 } : {},
- {
- model: User,
- },
- );
- expect(stub.callCount).to.eq(1);
- const firstCall = stub.getCall(0);
- expectsql(firstCall.args[0], {
- default:
- 'INSERT INTO `Users` (`firstName`,`counter`) VALUES ($sequelize_1,`counter` + 1) ON DUPLICATE KEY UPDATE `counter`=`counter` + 1;',
- postgres:
- 'INSERT INTO "Users" ("firstName","counter") VALUES ($sequelize_1,`counter` + 1) ON CONFLICT ("id") DO UPDATE SET "counter"=EXCLUDED."counter";',
- mssql: `
- MERGE INTO [Users] WITH(HOLDLOCK) AS [Users_target]
- USING (VALUES(N'Jonh', \`counter\` + 1)) AS [Users_source]([firstName], [counter])
- ON [Users_target].[id] = [Users_source].[id] WHEN MATCHED THEN UPDATE SET [Users_target].[counter] = \`counter\` + 1
- WHEN NOT MATCHED THEN INSERT ([firstName], [counter]) VALUES(N'Jonh', \`counter\` + 1) OUTPUT $action, INSERTED.*;
- `,
- sqlite3:
- 'INSERT INTO `Users` (`firstName`,`counter`) VALUES ($sequelize_1,`counter` + 1) ON CONFLICT (`id`) DO UPDATE SET `counter`=EXCLUDED.`counter`;',
- db2: `
- MERGE INTO "Users" AS "Users_target"
- USING (VALUES('Jonh', \`counter\` + 1)) AS "Users_source"("firstName", "counter")
- ON "Users_target"."id" = "Users_source"."id" WHEN MATCHED THEN UPDATE SET "Users_target"."counter" = \`counter\` + 1
- WHEN NOT MATCHED THEN INSERT ("firstName", "counter") VALUES('Jonh', \`counter\` + 1);
- `,
- });
- });
- });
|