1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636 |
- 'use strict';
- const range = require('lodash/range');
- const chai = require('chai');
- const sinon = require('sinon');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes, Op, Sequelize, sql } = require('@sequelize/core');
- const delay = require('delay');
- const assert = require('node:assert');
- const pTimeout = require('p-timeout');
- const current = Support.sequelize;
- const dialect = current.dialect;
- const dialectName = Support.getTestDialect();
- describe(Support.getTestDialectTeaser('Model'), () => {
- beforeEach(async function () {
- const sequelize = await Support.createMultiTransactionalTestSequelizeInstance(this.sequelize);
- this.customSequelize = sequelize;
- this.User = this.customSequelize.define('User', {
- username: DataTypes.STRING,
- secretValue: DataTypes.STRING,
- data: DataTypes.STRING,
- intVal: DataTypes.INTEGER,
- theDate: DataTypes.DATE,
- aBool: DataTypes.BOOLEAN,
- uniqueName: { type: DataTypes.STRING, unique: true },
- });
- this.Account = this.customSequelize.define('Account', {
- accountName: DataTypes.STRING,
- });
- this.Student = this.customSequelize.define('Student', {
- no: { type: DataTypes.INTEGER, primaryKey: true },
- name: { type: DataTypes.STRING, allowNull: false },
- });
- await this.customSequelize.sync({ force: true });
- });
- afterEach(function () {
- return this.customSequelize.close();
- });
- // TODO: move to own suite
- describe('findOrCreate', () => {
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const t = await this.customSequelize.startUnmanagedTransaction();
- await this.User.findOrCreate({
- where: {
- username: 'Username',
- },
- defaults: {
- data: 'some data',
- },
- transaction: t,
- });
- const count = await this.User.count();
- expect(count).to.equal(0);
- await t.commit();
- const count0 = await this.User.count();
- expect(count0).to.equal(1);
- });
- it('supports more than one models per transaction', async function () {
- const t = await this.customSequelize.startUnmanagedTransaction();
- await this.User.findOrCreate({
- where: { username: 'Username' },
- defaults: { data: 'some data' },
- transaction: t,
- });
- await this.Account.findOrCreate({ where: { accountName: 'accountName' }, transaction: t });
- await t.commit();
- });
- }
- it('should error correctly when defaults contain a unique key', async function () {
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.STRING,
- unique: true,
- },
- username: {
- type: DataTypes.STRING,
- unique: true,
- },
- });
- await User.sync({ force: true });
- await User.create({
- username: 'gottlieb',
- });
- await expect(
- User.findOrCreate({
- where: {
- objectId: 'asdasdasd',
- },
- defaults: {
- username: 'gottlieb',
- },
- }),
- ).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError);
- });
- it('should error correctly when defaults contain a unique key and a non-existent field', async function () {
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.STRING,
- unique: true,
- },
- username: {
- type: DataTypes.STRING,
- unique: true,
- },
- });
- await User.sync({ force: true });
- await User.create({
- username: 'gottlieb',
- });
- await expect(
- User.findOrCreate({
- where: {
- objectId: 'asdasdasd',
- },
- defaults: {
- username: 'gottlieb',
- foo: 'bar', // field that's not a defined attribute
- bar: 121,
- },
- }),
- ).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError);
- });
- it('should error correctly when defaults contain a unique key and the where clause is complex', async function () {
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.STRING,
- unique: true,
- },
- username: {
- type: DataTypes.STRING,
- unique: true,
- },
- });
- await User.sync({ force: true });
- await User.create({ username: 'gottlieb' });
- try {
- await User.findOrCreate({
- where: {
- [Op.or]: [
- {
- objectId: 'asdasdasd1',
- },
- {
- objectId: 'asdasdasd2',
- },
- ],
- },
- defaults: {
- username: 'gottlieb',
- },
- });
- } catch (error) {
- expect(error).to.be.instanceof(Sequelize.UniqueConstraintError);
- if (dialectName !== 'ibmi') {
- expect(error.errors[0].path).to.be.a('string', 'username');
- }
- }
- });
- it('should work with empty uuid primary key in where', async function () {
- const User = this.customSequelize.define('User', {
- id: {
- type: DataTypes.UUID,
- primaryKey: true,
- allowNull: false,
- defaultValue: sql.uuidV4,
- },
- name: {
- type: DataTypes.STRING,
- },
- });
- await User.sync({ force: true });
- await User.findOrCreate({
- where: {},
- defaults: {
- name: Math.random().toString(),
- },
- });
- });
- if (!['sqlite3', 'mssql', 'db2', 'ibmi'].includes(current.dialect.name)) {
- it('should not deadlock with no existing entries and no outer transaction', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- unique: 'company_user_email',
- },
- companyId: {
- type: DataTypes.INTEGER,
- unique: 'company_user_email',
- },
- });
- await User.sync({ force: true });
- await Promise.all(
- range(50).map(i => {
- return User.findOrCreate({
- where: {
- email: `unique.email.${i}@sequelizejs.com`,
- companyId: Math.floor(Math.random() * 5),
- },
- });
- }),
- );
- });
- it('should not deadlock with existing entries and no outer transaction', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- unique: 'company_user_email',
- },
- companyId: {
- type: DataTypes.INTEGER,
- unique: 'company_user_email',
- },
- });
- await User.sync({ force: true });
- await Promise.all(
- range(50).map(i => {
- return User.findOrCreate({
- where: {
- email: `unique.email.${i}@sequelizejs.com`,
- companyId: 2,
- },
- });
- }),
- );
- await Promise.all(
- range(50).map(i => {
- return User.findOrCreate({
- where: {
- email: `unique.email.${i}@sequelizejs.com`,
- companyId: 2,
- },
- });
- }),
- );
- });
- it('should not deadlock with concurrency duplicate entries and no outer transaction', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- unique: 'company_user_email',
- },
- companyId: {
- type: DataTypes.INTEGER,
- unique: 'company_user_email',
- },
- });
- await User.sync({ force: true });
- await Promise.all(
- range(50).map(() => {
- return User.findOrCreate({
- where: {
- email: 'unique.email.1@sequelizejs.com',
- companyId: 2,
- },
- });
- }),
- );
- });
- }
- it('should support special characters in defaults', async function () {
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.INTEGER,
- unique: true,
- },
- description: {
- type: DataTypes.TEXT,
- },
- });
- await User.sync({ force: true });
- await User.findOrCreate({
- where: {
- objectId: 1,
- },
- defaults: {
- description: "$$ and !! and :: and ? and ^ and * and '",
- },
- });
- });
- it('should support bools in defaults', async function () {
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.INTEGER,
- unique: true,
- },
- bool: DataTypes.BOOLEAN,
- });
- await User.sync({ force: true });
- await User.findOrCreate({
- where: {
- objectId: 1,
- },
- defaults: {
- bool: false,
- },
- });
- });
- it('returns instance if already existent. Single find field.', async function () {
- const data = {
- username: 'Username',
- };
- const user = await this.User.create(data);
- const [_user, created] = await this.User.findOrCreate({
- where: {
- username: user.username,
- },
- });
- expect(_user.id).to.equal(user.id);
- expect(_user.username).to.equal('Username');
- expect(created).to.be.false;
- });
- it('Returns instance if already existent. Multiple find fields.', async function () {
- const data = {
- username: 'Username',
- data: 'ThisIsData',
- };
- const user = await this.User.create(data);
- const [_user, created] = await this.User.findOrCreate({ where: data });
- expect(_user.id).to.equal(user.id);
- expect(_user.username).to.equal('Username');
- expect(_user.data).to.equal('ThisIsData');
- expect(created).to.be.false;
- });
- it('does not include exception catcher in response', async function () {
- const data = {
- username: 'Username',
- data: 'ThisIsData',
- };
- const [user0] = await this.User.findOrCreate({
- where: data,
- defaults: {},
- });
- expect(user0.dataValues.sequelize_caught_exception).to.be.undefined;
- const [user] = await this.User.findOrCreate({
- where: data,
- defaults: {},
- });
- expect(user.dataValues.sequelize_caught_exception).to.be.undefined;
- });
- it('creates new instance with default value.', async function () {
- const data = {
- username: 'Username',
- };
- const default_values = {
- data: 'ThisIsData',
- };
- const [user, created] = await this.User.findOrCreate({
- where: data,
- defaults: default_values,
- });
- expect(user.username).to.equal('Username');
- expect(user.data).to.equal('ThisIsData');
- expect(created).to.be.true;
- });
- it('supports .or() (only using default values)', async function () {
- const [user, created] = await this.User.findOrCreate({
- where: Sequelize.or({ username: 'Fooobzz' }, { secretValue: 'Yolo' }),
- defaults: { username: 'Fooobzz', secretValue: 'Yolo' },
- });
- expect(user.username).to.equal('Fooobzz');
- expect(user.secretValue).to.equal('Yolo');
- expect(created).to.be.true;
- });
- it('should ignore option returning', async function () {
- const [user, created] = await this.User.findOrCreate({
- where: { username: 'Username' },
- defaults: { data: 'ThisIsData' },
- returning: false,
- });
- expect(user.username).to.equal('Username');
- expect(user.data).to.equal('ThisIsData');
- expect(created).to.be.true;
- });
- if (current.dialect.supports.transactions) {
- it('should release transaction when meeting errors', async function () {
- const test = async times => {
- if (times > 10) {
- return true;
- }
- try {
- return await pTimeout(
- this.Student.findOrCreate({
- where: {
- no: 1,
- },
- }),
- 1000,
- );
- } catch (error) {
- if (error instanceof Sequelize.ValidationError) {
- return test(times + 1);
- }
- if (error instanceof pTimeout.TimeoutError) {
- throw new TypeError(error);
- }
- throw error;
- }
- };
- await test(0);
- });
- }
- describe('several concurrent calls', () => {
- if (current.dialect.supports.transactions) {
- it('works with a transaction', async function () {
- const transaction = await this.customSequelize.startUnmanagedTransaction();
- const [first, second] = await Promise.all([
- this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }),
- this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }),
- ]);
- const firstInstance = first[0];
- const firstCreated = first[1];
- const secondInstance = second[0];
- const secondCreated = second[1];
- // Depending on execution order and MAGIC either the first OR the second call should return true
- expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR
- expect(firstInstance).to.be.ok;
- expect(secondInstance).to.be.ok;
- expect(firstInstance.id).to.equal(secondInstance.id);
- await transaction.commit();
- });
- }
- it('should not fail silently with concurrency higher than pool, a unique constraint and a create hook resulting in mismatched values', async function () {
- if (['sqlite3', 'mssql', 'db2', 'ibmi'].includes(dialectName)) {
- return;
- }
- const User = this.customSequelize.define('user', {
- username: {
- type: DataTypes.STRING,
- unique: true,
- columnName: 'user_name',
- },
- });
- User.beforeCreate(instance => {
- instance.username += ' h.';
- });
- const spy = sinon.spy();
- const names = ['mick', 'mick', 'mick', 'mick', 'mick', 'mick', 'mick'];
- await User.sync({ force: true });
- await Promise.all(
- names.map(async username => {
- try {
- return await User.findOrCreate({ where: { username } });
- } catch (error) {
- spy();
- expect(error.message).to.equal(
- `user#findOrCreate: value used for username was not equal for both the find and the create calls, 'mick' vs 'mick h.'`,
- );
- }
- }),
- );
- expect(spy).to.have.been.called;
- });
- it('should error correctly when defaults contain a unique key without a transaction', async function () {
- if (dialectName === 'sqlite3') {
- return;
- }
- const User = this.customSequelize.define('user', {
- objectId: {
- type: DataTypes.STRING,
- unique: true,
- },
- username: {
- type: DataTypes.STRING,
- unique: true,
- },
- });
- await User.sync({ force: true });
- await User.create({
- username: 'gottlieb',
- });
- return Promise.all([
- (async () => {
- const error = await expect(
- User.findOrCreate({
- where: {
- objectId: 'asdasdasd',
- },
- defaults: {
- username: 'gottlieb',
- },
- }),
- ).to.be.rejectedWith(Sequelize.UniqueConstraintError);
- expect(error.fields).to.be.ok;
- })(),
- (async () => {
- const error = await expect(
- User.findOrCreate({
- where: {
- objectId: 'asdasdasd',
- },
- defaults: {
- username: 'gottlieb',
- },
- }),
- ).to.be.rejectedWith(Sequelize.UniqueConstraintError);
- expect(error.fields).to.be.ok;
- })(),
- ]);
- });
- it('works without a transaction', async function () {
- // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off
- if (dialectName === 'sqlite3') {
- return;
- }
- const [first, second] = await Promise.all([
- this.User.findOrCreate({ where: { uniqueName: 'winner' } }),
- this.User.findOrCreate({ where: { uniqueName: 'winner' } }),
- ]);
- const firstInstance = first[0];
- const firstCreated = first[1];
- const secondInstance = second[0];
- const secondCreated = second[1];
- // Depending on execution order and MAGIC either the first OR the second call should return true
- expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR
- expect(firstInstance).to.be.ok;
- expect(secondInstance).to.be.ok;
- expect(firstInstance.id).to.equal(secondInstance.id);
- });
- });
- });
- // TODO: move to own suite
- describe('findCreateFind', () => {
- if (dialectName !== 'sqlite3') {
- it('should work with multiple concurrent calls', async function () {
- const [[instance1, created1], [instance2, created2], [instance3, created3]] =
- await Promise.all([
- this.User.findCreateFind({ where: { uniqueName: 'winner' } }),
- this.User.findCreateFind({ where: { uniqueName: 'winner' } }),
- this.User.findCreateFind({ where: { uniqueName: 'winner' } }),
- ]);
- // All instances are the same
- expect(instance1.id).to.equal(instance2.id);
- expect(instance2.id).to.equal(instance3.id);
- // Only one of the createdN values is true
- expect(Boolean(created1 ^ created2 ^ created3)).to.be.true;
- });
- if (current.dialect.supports.transactions) {
- it('should work with multiple concurrent calls within a transaction', async function () {
- const t = await this.customSequelize.startUnmanagedTransaction();
- const [[instance1, created1], [instance2, created2], [instance3, created3]] =
- await Promise.all([
- this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }),
- this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }),
- this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }),
- ]);
- await t.commit();
- // All instances are the same
- expect(instance1.id).to.equal(1);
- expect(instance2.id).to.equal(1);
- expect(instance3.id).to.equal(1);
- // Only one of the createdN values is true
- expect(Boolean(created1 ^ created2 ^ created3)).to.be.true;
- });
- }
- }
- });
- describe('create', () => {
- it('works with multiple non-integer primary keys with a default value', async function () {
- const User = this.customSequelize.define('User', {
- id1: {
- primaryKey: true,
- type: DataTypes.UUID,
- defaultValue: sql.uuidV4,
- },
- id2: {
- primaryKey: true,
- type: DataTypes.UUID,
- defaultValue: sql.uuidV4,
- },
- email: {
- type: DataTypes.UUID,
- defaultValue: sql.uuidV4,
- },
- });
- await this.customSequelize.sync({ force: true });
- const user = await User.create({});
- expect(user).to.be.ok;
- expect(user.id1).to.be.ok;
- expect(user.id2).to.be.ok;
- });
- it('should return an error for a unique constraint error', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- unique: { name: 'email', msg: 'Email is already registered.' },
- validate: {
- notEmpty: true,
- isEmail: true,
- },
- },
- });
- await this.customSequelize.sync({ force: true });
- await User.create({ email: 'hello@sequelize.com' });
- try {
- await User.create({ email: 'hello@sequelize.com' });
- assert(false);
- } catch (error) {
- expect(error).to.be.ok;
- expect(error).to.be.an.instanceof(Error);
- }
- });
- it('runs validation', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- validate: {
- isEmail: true,
- },
- },
- });
- const error = await expect(User.create({ email: 'invalid' })).to.be.rejectedWith(
- Sequelize.ValidationError,
- );
- expect(error.get('email')).to.be.instanceof(Array);
- expect(error.get('email')[0]).to.exist;
- expect(error.get('email')[0].message).to.equal('Validation isEmail on email failed');
- });
- it('works without any primary key', async function () {
- const Log = this.customSequelize.define('log', {
- level: DataTypes.STRING,
- });
- Log.removeAttribute('id');
- await this.customSequelize.sync({ force: true });
- await Promise.all([
- Log.create({ level: 'info' }),
- Log.bulkCreate([{ level: 'error' }, { level: 'debug' }]),
- ]);
- const logs = await Log.findAll();
- for (const log of logs) {
- expect(log.get('id')).not.to.be.ok;
- }
- });
- it('should be able to set createdAt and updatedAt if using silent: true', async function () {
- const User = this.customSequelize.define(
- 'user',
- {
- name: DataTypes.STRING,
- },
- {
- timestamps: true,
- },
- );
- const createdAt = new Date(2012, 10, 10, 10, 10, 10);
- const updatedAt = new Date(2011, 11, 11, 11, 11, 11);
- await User.sync({ force: true });
- const user = await User.create(
- {
- createdAt,
- updatedAt,
- },
- {
- silent: true,
- },
- );
- expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime());
- expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime());
- const user0 = await User.findOne({
- where: {
- updatedAt: {
- [Op.ne]: null,
- },
- },
- });
- expect(createdAt.getTime()).to.equal(user0.get('createdAt').getTime());
- expect(updatedAt.getTime()).to.equal(user0.get('updatedAt').getTime());
- });
- it('works with custom timestamps with a default value', async function () {
- const User = this.customSequelize.define(
- 'User',
- {
- username: DataTypes.STRING,
- date_of_birth: DataTypes.DATE,
- email: DataTypes.STRING,
- password: DataTypes.STRING,
- created_time: {
- type: DataTypes.DATE(3),
- allowNull: false,
- defaultValue: DataTypes.NOW,
- },
- updated_time: {
- type: DataTypes.DATE(3),
- allowNull: false,
- defaultValue: DataTypes.NOW,
- },
- },
- {
- createdAt: 'created_time',
- updatedAt: 'updated_time',
- tableName: 'users',
- underscored: true,
- freezeTableName: true,
- force: false,
- },
- );
- await this.customSequelize.sync({ force: true });
- const user1 = await User.create({});
- await delay(10);
- const user2 = await User.create({});
- for (const user of [user1, user2]) {
- expect(user).to.be.ok;
- expect(user.created_time).to.be.ok;
- expect(user.updated_time).to.be.ok;
- }
- // Timestamps should have milliseconds. However, there is a small chance that
- // it really is 0 for one of them, by coincidence. So we check twice with two
- // users created almost at the same time.
- expect([
- user1.created_time.getMilliseconds(),
- user2.created_time.getMilliseconds(),
- ]).not.to.deep.equal([0, 0]);
- expect([
- user1.updated_time.getMilliseconds(),
- user2.updated_time.getMilliseconds(),
- ]).not.to.deep.equal([0, 0]);
- });
- it('works with custom timestamps and underscored', async function () {
- const User = this.customSequelize.define(
- 'User',
- {},
- {
- createdAt: 'createdAt',
- updatedAt: 'updatedAt',
- underscored: true,
- },
- );
- await this.customSequelize.sync({ force: true });
- const user = await User.create({});
- expect(user).to.be.ok;
- expect(user.createdAt).to.be.ok;
- expect(user.updatedAt).to.be.ok;
- expect(user.created_at).not.to.be.ok;
- expect(user.updated_at).not.to.be.ok;
- });
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const t = await this.customSequelize.startUnmanagedTransaction();
- await this.User.create({ username: 'user' }, { transaction: t });
- const count = await this.User.count();
- expect(count).to.equal(0);
- await t.commit();
- const count0 = await this.User.count();
- expect(count0).to.equal(1);
- });
- }
- if (current.dialect.supports.returnValues) {
- describe('return values', () => {
- it('should make the autoincremented values available on the returned instances', async function () {
- const User = this.customSequelize.define('user', {});
- await User.sync({ force: true });
- const user = await User.create({}, { returning: true });
- expect(user.get('id')).to.be.ok;
- expect(user.get('id')).to.equal(1);
- });
- it('should make the autoincremented values available on the returned instances with custom fields', async function () {
- const User = this.customSequelize.define('user', {
- maId: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- field: 'yo_id',
- },
- });
- await User.sync({ force: true });
- const user = await User.create({}, { returning: true });
- expect(user.get('maId')).to.be.ok;
- expect(user.get('maId')).to.equal(1);
- });
- });
- }
- it('is possible to use casting when creating an instance', async function () {
- const type = ['mysql', 'mariadb'].includes(dialectName) ? 'signed' : 'integer';
- const bindParam =
- dialectName === 'postgres'
- ? '$1'
- : dialectName === 'sqlite3'
- ? '$sequelize_1'
- : dialectName === 'mssql'
- ? '@sequelize_1'
- : '?';
- let match = false;
- const user = await this.User.create(
- {
- intVal: this.customSequelize.cast('1', type),
- },
- {
- logging(sql) {
- expect(sql).to.include(`CAST(${bindParam} AS ${type.toUpperCase()})`);
- match = true;
- },
- },
- );
- const user0 = await this.User.findByPk(user.id);
- expect(user0.intVal).to.equal(1);
- expect(match).to.equal(true);
- });
- it('is possible to use casting multiple times mixed in with other utilities', async function () {
- let type = this.customSequelize.cast(
- this.customSequelize.cast(this.customSequelize.literal('1-2'), 'integer'),
- 'integer',
- );
- let match = false;
- if (['mysql', 'mariadb'].includes(dialectName)) {
- type = this.customSequelize.cast(
- this.customSequelize.cast(this.customSequelize.literal('1-2'), 'unsigned'),
- 'signed',
- );
- }
- const user = await this.User.create(
- {
- intVal: type,
- },
- {
- logging(sql) {
- if (['mysql', 'mariadb'].includes(dialectName)) {
- expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)');
- } else {
- expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)');
- }
- match = true;
- },
- },
- );
- const user0 = await this.User.findByPk(user.id);
- expect(user0.intVal).to.equal(-1);
- expect(match).to.equal(true);
- });
- it('is possible to just use .literal() to bypass escaping', async function () {
- const user = await this.User.create({
- intVal: this.customSequelize.literal(
- `CAST(1-2 AS ${dialectName === 'mysql' ? 'SIGNED' : 'INTEGER'})`,
- ),
- });
- const user0 = await this.User.findByPk(user.id);
- expect(user0.intVal).to.equal(-1);
- });
- it('is possible to use funtions when creating an instance', async function () {
- const user = await this.User.create({
- secretValue: this.customSequelize.fn('upper', 'sequelize'),
- });
- const user0 = await this.User.findByPk(user.id);
- expect(user0.secretValue).to.equal('SEQUELIZE');
- });
- it('should escape $ in sequelize functions arguments', async function () {
- const user = await this.User.create({
- secretValue: this.customSequelize.fn('upper', '$sequelize'),
- });
- const user0 = await this.User.findByPk(user.id);
- expect(user0.secretValue).to.equal('$SEQUELIZE');
- });
- it('should work with a non-id named uuid primary key columns', async function () {
- const Monkey = this.customSequelize.define('Monkey', {
- monkeyId: {
- type: DataTypes.UUID,
- primaryKey: true,
- defaultValue: sql.uuidV4,
- allowNull: false,
- },
- });
- await this.customSequelize.sync({ force: true });
- const monkey = await Monkey.create();
- expect(monkey.get('monkeyId')).to.be.ok;
- });
- it('is possible to use functions as default values', async function () {
- let userWithDefaults;
- if (dialectName.startsWith('postgres')) {
- await this.customSequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
- userWithDefaults = this.customSequelize.define('userWithDefaults', {
- uuid: {
- type: 'UUID',
- defaultValue: this.customSequelize.fn('uuid_generate_v4'),
- },
- });
- await userWithDefaults.sync({ force: true });
- const user = await userWithDefaults.create({});
- // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016
- expect(user.uuid).to.match(
- /^[\da-f]{8}-[\da-f]{4}-[1-5][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,
- );
- return;
- }
- if (dialectName === 'sqlite3') {
- // The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name
- // to enclose the date function in (). http://www.sqlite.org/syntaxdiagrams.html#column-constraint
- userWithDefaults = this.customSequelize.define('userWithDefaults', {
- year: {
- type: DataTypes.STRING,
- defaultValue: this.customSequelize.fn('', this.customSequelize.fn('date', 'now')),
- },
- });
- await userWithDefaults.sync({ force: true });
- const user = await userWithDefaults.create({});
- const user0 = await userWithDefaults.findByPk(user.id);
- const now = new Date();
- const pad = number => number.toString().padStart(2, '0');
- expect(user0.year).to.equal(
- `${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`,
- );
- }
- // functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016
- });
- if (dialectName === 'postgres') {
- it('does not cast arrays for postgresql insert', async function () {
- const User = this.customSequelize.define('UserWithArray', {
- myvals: { type: DataTypes.ARRAY(DataTypes.INTEGER) },
- mystr: { type: DataTypes.ARRAY(DataTypes.STRING) },
- });
- let test = false;
- await User.sync({ force: true });
- await User.create(
- { myvals: [], mystr: [] },
- {
- logging(sql) {
- test = true;
- expect(sql).to.contain(
- 'INSERT INTO "UserWithArrays" ("id","myvals","mystr","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4)',
- );
- },
- },
- );
- expect(test).to.be.true;
- });
- it('does not cast arrays for postgres update', async function () {
- const User = this.customSequelize.define('UserWithArray', {
- myvals: { type: DataTypes.ARRAY(DataTypes.INTEGER) },
- mystr: { type: DataTypes.ARRAY(DataTypes.STRING) },
- });
- let test = false;
- await User.sync({ force: true });
- const user = await User.create({
- myvals: [1, 2, 3, 4],
- mystr: ['One', 'Two', 'Three', 'Four'],
- });
- user.myvals = [];
- user.mystr = [];
- await user.save({
- logging(sql) {
- test = true;
- expect(sql).to.contain(
- 'UPDATE "UserWithArrays" SET "myvals"=$1,"mystr"=$2,"updatedAt"=$3 WHERE "id" = $4',
- );
- },
- });
- expect(test).to.be.true;
- });
- }
- it("doesn't allow duplicated records with unique:true", async function () {
- const User = this.customSequelize.define('UserWithUniqueUsername', {
- username: { type: DataTypes.STRING, unique: true },
- });
- await User.sync({ force: true });
- await User.create({ username: 'foo' });
- try {
- await User.create({ username: 'foo' });
- } catch (error) {
- if (!(error instanceof Sequelize.UniqueConstraintError)) {
- throw error;
- }
- expect(error).to.be.ok;
- }
- });
- if (dialect.supports.dataTypes.CITEXT) {
- it(`doesn't allow case-insensitive duplicated records using CITEXT`, async function () {
- const User = this.customSequelize.define('UserWithUniqueCITEXT', {
- username: { type: DataTypes.CITEXT, unique: true },
- });
- try {
- await User.sync({ force: true });
- await User.create({ username: 'foo' });
- await User.create({ username: 'fOO' });
- } catch (error) {
- if (!(error instanceof Sequelize.UniqueConstraintError)) {
- throw error;
- }
- expect(error).to.be.ok;
- }
- });
- }
- if (dialectName === 'postgres') {
- it('allows the creation of a TSVECTOR field', async function () {
- const User = this.customSequelize.define('UserWithTSVECTOR', {
- name: DataTypes.TSVECTOR,
- });
- await User.sync({ force: true });
- await User.create({ name: 'John Doe' });
- });
- it('TSVECTOR only allow string', async function () {
- const User = this.customSequelize.define('UserWithTSVECTOR', {
- username: { type: DataTypes.TSVECTOR },
- });
- try {
- await User.sync({ force: true });
- await User.create({ username: 42 });
- } catch (error) {
- if (!(error instanceof Sequelize.ValidationError)) {
- throw error;
- }
- expect(error).to.be.ok;
- }
- });
- }
- if (current.dialect.supports.index.functionBased) {
- it(`doesn't allow duplicated records with unique function based indexes`, async function () {
- const User = this.customSequelize.define('UserWithUniqueUsernameFunctionIndex', {
- username: DataTypes.STRING,
- email: { type: DataTypes.STRING, unique: true },
- });
- try {
- await User.sync({ force: true });
- await this.customSequelize.query(
- `CREATE UNIQUE INDEX lower_case_username ON ${this.customSequelize.queryGenerator.quoteTable(User)} ((lower("username")))`,
- );
- await User.create({ username: 'foo' });
- await User.create({ username: 'foo' });
- } catch (error) {
- if (!(error instanceof Sequelize.UniqueConstraintError)) {
- throw error;
- }
- expect(error).to.be.ok;
- }
- });
- }
- it('raises an error if created object breaks definition constraints', async function () {
- const UserNull = this.customSequelize.define('UserWithNonNullSmth', {
- username: { type: DataTypes.STRING, unique: true },
- smth: { type: DataTypes.STRING, allowNull: false },
- });
- await UserNull.sync({ force: true });
- try {
- await UserNull.create({ username: 'foo2', smth: null });
- } catch (error) {
- expect(error).to.exist;
- const smth1 = error.get('smth')[0] || {};
- expect(smth1.path).to.equal('smth');
- expect(smth1.type || smth1.origin).to.match(/notNull violation/);
- }
- });
- it('raises an error if created object breaks definition constraints', async function () {
- const UserNull = this.customSequelize.define('UserWithNonNullSmth', {
- username: { type: DataTypes.STRING, unique: true },
- smth: { type: DataTypes.STRING, allowNull: false },
- });
- await UserNull.sync({ force: true });
- await UserNull.create({ username: 'foo', smth: 'foo' });
- try {
- await UserNull.create({ username: 'foo', smth: 'bar' });
- } catch (error) {
- if (!(error instanceof Sequelize.UniqueConstraintError)) {
- throw error;
- }
- expect(error).to.be.ok;
- }
- });
- it('raises an error if saving an empty string into a column allowing null or URL', async function () {
- const StringIsNullOrUrl = this.customSequelize.define('StringIsNullOrUrl', {
- str: { type: DataTypes.STRING, allowNull: true, validate: { isURL: true } },
- });
- await StringIsNullOrUrl.sync({ force: true });
- const str1 = await StringIsNullOrUrl.create({ str: null });
- expect(str1.str).to.be.null;
- const str2 = await StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' });
- expect(str2.str).to.equal('http://sequelizejs.org');
- try {
- await StringIsNullOrUrl.create({ str: '' });
- } catch (error) {
- expect(error).to.exist;
- expect(error.get('str')[0].message).to.match(/Validation isURL on str failed/);
- }
- });
- if (current.dialect.supports.dataTypes.BIGINT) {
- it('sets a 64 bit int in bigint', async function () {
- const User = this.customSequelize.define('UserWithBigIntFields', {
- big: DataTypes.BIGINT,
- });
- await User.sync({ force: true });
- const user = await User.create({ big: '9223372036854775807' });
- expect(user.big).to.equal('9223372036854775807');
- });
- }
- it('sets auto increment fields', async function () {
- const User = this.customSequelize.define('UserWithAutoIncrementField', {
- userid: {
- type: DataTypes.INTEGER,
- autoIncrement: true,
- primaryKey: true,
- allowNull: false,
- },
- });
- await User.sync({ force: true });
- const user = await User.create({});
- expect(user.userid).to.equal(1);
- const user0 = await User.create({});
- expect(user0.userid).to.equal(2);
- });
- it('allows the usage of options as attribute', async function () {
- const User = this.customSequelize.define('UserWithNameAndOptions', {
- name: DataTypes.STRING,
- options: DataTypes.TEXT,
- });
- const options = JSON.stringify({ foo: 'bar', bar: 'foo' });
- await User.sync({ force: true });
- const user = await User.create({ name: 'John Doe', options });
- expect(user.options).to.equal(options);
- });
- it('allows sql logging', async function () {
- const User = this.customSequelize.define('UserWithUniqueNameAndNonNullSmth', {
- name: { type: DataTypes.STRING, unique: true },
- smth: { type: DataTypes.STRING, allowNull: false },
- });
- let test = false;
- await User.sync({ force: true });
- await User.create(
- { name: 'Fluffy Bunny', smth: 'else' },
- {
- logging(sql) {
- expect(sql).to.exist;
- test = true;
- expect(sql.toUpperCase()).to.contain('INSERT');
- },
- },
- );
- expect(test).to.be.true;
- });
- it('should only store the values passed in the whitelist', async function () {
- // A unique column do not accept NULL in Db2. Unique column must have value in insert statement.
- const data = { username: 'Peter', secretValue: '42', uniqueName: 'name' };
- const fields =
- dialectName === 'db2' ? { fields: ['username', 'uniqueName'] } : { fields: ['username'] };
- const user = await this.User.create(data, fields);
- const _user = await this.User.findByPk(user.id);
- expect(_user.username).to.equal(data.username);
- expect(_user.secretValue).not.to.equal(data.secretValue);
- expect(_user.secretValue).to.equal(null);
- });
- it('should store all values if no whitelist is specified', async function () {
- const data = { username: 'Peter', secretValue: '42' };
- const user = await this.User.create(data);
- const _user = await this.User.findByPk(user.id);
- expect(_user.username).to.equal(data.username);
- expect(_user.secretValue).to.equal(data.secretValue);
- });
- it('can omit autoincremental columns', async function () {
- const data = { title: 'Iliad' };
- const dataTypes = [DataTypes.INTEGER];
- const sync = [];
- const promises = [];
- const books = [];
- if (current.dialect.supports.dataTypes.BIGINT) {
- dataTypes.push(DataTypes.BIGINT);
- }
- for (const [index, dataType] of dataTypes.entries()) {
- books[index] = this.customSequelize.define(`Book${index}`, {
- id: { type: dataType, primaryKey: true, autoIncrement: true },
- title: DataTypes.TEXT,
- });
- }
- for (const b of books) {
- sync.push(b.sync({ force: true }));
- }
- await Promise.all(sync);
- for (const [index, b] of books.entries()) {
- promises.push(
- (async () => {
- const book = await b.create(data);
- expect(book.title).to.equal(data.title);
- expect(book.author).to.equal(data.author);
- expect(books[index].getAttributes().id.type instanceof dataTypes[index]).to.be.ok;
- })(),
- );
- }
- await Promise.all(promises);
- });
- it('saves data with single quote', async function () {
- const quote = "single'quote";
- const user = await this.User.create({ data: quote });
- expect(user.data).to.equal(quote);
- const user0 = await this.User.findOne({ where: { id: user.id } });
- expect(user0.data).to.equal(quote);
- });
- it('saves data with double quote', async function () {
- const quote = 'double"quote';
- const user = await this.User.create({ data: quote });
- expect(user.data).to.equal(quote);
- const user0 = await this.User.findOne({ where: { id: user.id } });
- expect(user0.data).to.equal(quote);
- });
- it('saves stringified JSON data', async function () {
- const json = JSON.stringify({ key: 'value' });
- const user = await this.User.create({ data: json });
- expect(user.data).to.equal(json);
- const user0 = await this.User.findOne({ where: { id: user.id } });
- expect(user0.data).to.equal(json);
- });
- it('stores the current date in createdAt', async function () {
- const user = await this.User.create({ username: 'foo' });
- expect(Number.parseInt(Number(user.createdAt) / 5000, 10)).to.be.closeTo(
- Number.parseInt(Date.now() / 5000, 10),
- 1.5,
- );
- });
- it('allows setting custom IDs', async function () {
- const user = await this.User.create({ id: 42 });
- expect(user.id).to.equal(42);
- const user0 = await this.User.findByPk(42);
- expect(user0).to.exist;
- });
- it('should allow blank creates (with timestamps: false)', async function () {
- const Worker = this.customSequelize.define('Worker', {}, { timestamps: false });
- await Worker.sync();
- const worker = await Worker.create({}, { fields: [] });
- expect(worker).to.be.ok;
- });
- it('should allow truly blank creates', async function () {
- const Worker = this.customSequelize.define('Worker', {}, { timestamps: false });
- await Worker.sync();
- const worker = await Worker.create({}, { fields: [] });
- expect(worker).to.be.ok;
- });
- it('should only set passed fields', async function () {
- const User = this.customSequelize.define('User', {
- email: {
- type: DataTypes.STRING,
- },
- name: {
- type: DataTypes.STRING,
- },
- });
- await this.customSequelize.sync({ force: true });
- const user = await User.create(
- {
- name: 'Yolo Bear',
- email: 'yolo@bear.com',
- },
- {
- fields: ['name'],
- },
- );
- expect(user.name).to.be.ok;
- expect(user.email).not.to.be.ok;
- const user0 = await User.findByPk(user.id);
- expect(user0.name).to.be.ok;
- expect(user0.email).not.to.be.ok;
- });
- it('Works even when SQL query has a values of transaction keywords such as BEGIN TRANSACTION', async function () {
- const Task = this.customSequelize.define('task', {
- title: DataTypes.STRING,
- });
- await Task.sync({ force: true });
- const newTasks = await Promise.all([
- Task.create({ title: 'BEGIN TRANSACTION' }),
- Task.create({ title: 'COMMIT TRANSACTION' }),
- Task.create({ title: 'ROLLBACK TRANSACTION' }),
- Task.create({ title: 'SAVE TRANSACTION' }),
- ]);
- expect(newTasks).to.have.lengthOf(4);
- expect(newTasks[0].title).to.equal('BEGIN TRANSACTION');
- expect(newTasks[1].title).to.equal('COMMIT TRANSACTION');
- expect(newTasks[2].title).to.equal('ROLLBACK TRANSACTION');
- expect(newTasks[3].title).to.equal('SAVE TRANSACTION');
- });
- describe('enums', () => {
- it('correctly restores enum values', async function () {
- const Item = this.customSequelize.define('Item', {
- state: { type: DataTypes.ENUM(['available', 'in_cart', 'shipped']) },
- });
- await Item.sync({ force: true });
- const _item = await Item.create({ state: 'available' });
- const item = await Item.findOne({ where: { state: 'available' } });
- expect(item.id).to.equal(_item.id);
- });
- it('allows null values', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: {
- type: DataTypes.ENUM(['happy', 'sad']),
- allowNull: true,
- },
- });
- await Enum.sync({ force: true });
- const _enum = await Enum.create({ state: null });
- expect(_enum.state).to.be.null;
- });
- describe('when defined via { field: DataTypes.ENUM }', () => {
- it('allows values passed as parameters', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: DataTypes.ENUM('happy', 'sad'),
- });
- await Enum.sync({ force: true });
- await Enum.create({ state: 'happy' });
- });
- it('allows values passed as an array', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: DataTypes.ENUM(['happy', 'sad']),
- });
- await Enum.sync({ force: true });
- await Enum.create({ state: 'happy' });
- });
- });
- describe('when defined via { field: { type: DataTypes.ENUM } }', () => {
- it('allows values passed as parameters', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: {
- type: DataTypes.ENUM('happy', 'sad'),
- },
- });
- await Enum.sync({ force: true });
- await Enum.create({ state: 'happy' });
- });
- it('allows values passed as an array', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: {
- type: DataTypes.ENUM(['happy', 'sad']),
- },
- });
- await Enum.sync({ force: true });
- await Enum.create({ state: 'happy' });
- });
- });
- describe('can safely sync multiple times', () => {
- it('through the factory', async function () {
- const Enum = this.customSequelize.define('Enum', {
- state: {
- type: DataTypes.ENUM(['happy', 'sad']),
- allowNull: true,
- },
- });
- await Enum.sync({ force: true });
- await Enum.sync();
- await Enum.sync({ force: true });
- });
- it('through sequelize', async function () {
- this.customSequelize.define('Enum', {
- state: {
- type: DataTypes.ENUM(['happy', 'sad']),
- allowNull: true,
- },
- });
- await this.customSequelize.sync({ force: true });
- await this.customSequelize.sync();
- await this.customSequelize.sync({ force: true });
- });
- });
- });
- });
- it('should return autoIncrement primary key (create)', async function () {
- const Maya = this.customSequelize.define('Maya', {});
- const M1 = {};
- await Maya.sync({ force: true });
- const m = await Maya.create(M1, { returning: true });
- expect(m.id).to.be.eql(1);
- });
- it('should support logging', async function () {
- const spy = sinon.spy();
- await this.User.create(
- {},
- {
- logging: spy,
- },
- );
- expect(spy.called).to.be.ok;
- });
- if (current.dialect.supports.returnValues) {
- it('should return default value set by the database (create)', async function () {
- const User = this.customSequelize.define('User', {
- name: DataTypes.STRING,
- code: { type: DataTypes.INTEGER, defaultValue: Sequelize.literal(2020) },
- });
- await User.sync({ force: true });
- const user = await User.create({ name: 'FooBar' });
- expect(user.name).to.equal('FooBar');
- expect(user.code).to.equal(2020);
- });
- }
- });
|