Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ language: node_js
branches:
only:
- master
- v5
- /^greenkeeper/.*$/
except:
- /^v\d+\.\d+\.\d+$/
Expand Down Expand Up @@ -76,16 +77,16 @@ jobs:
node_js: '8'
script:
- npm run semantic-release
before_deploy:
- npm run docs
deploy:
provider: surge
project: ./esdoc/
domain: docs.sequelizejs.com
skip_cleanup: true
# before_deploy:
# - npm run docs
# deploy:
# provider: surge
# project: ./esdoc/
# domain: docs.sequelizejs.com
# skip_cleanup: true

stages:
- lint
- test
- name: release
if: branch = master AND type = push AND fork = false
if: branch = v5 AND type = push AND fork = false
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ after_test:
branches:
only:
- master
- v5
- /^greenkeeper/.*$/
3 changes: 2 additions & 1 deletion lib/associations/belongs-to-many.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@ class BelongsToMany extends Association {
};

return this.get(sourceInstance, options).then(associatedObjects =>
_.differenceBy(instancePrimaryKeys, associatedObjects, this.targetKey).length === 0
_.differenceWith(instancePrimaryKeys, associatedObjects,
(a, b) => _.isEqual(a[this.targetKey], b[this.targetKey])).length === 0
);
}

Expand Down
6 changes: 5 additions & 1 deletion lib/dialects/abstract/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ class AbstractQuery {
_logQuery(sql, debugContext, parameters) {
const { connection, options } = this;
const benchmark = this.sequelize.options.benchmark || options.benchmark;
const instanceType = connection.queryType === 'read' ? 'replica' : 'primary';
const instanceTypeMessage = `on ${instanceType} DB`;
const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters;
const startTime = Date.now();
let logParameter = '';
Expand All @@ -355,7 +357,9 @@ class AbstractQuery {
const afterMsg = `Executed ${fmt}`;
debugContext(afterMsg);
if (benchmark) {
this.sequelize.log(afterMsg, Date.now() - startTime, options);
// this.sequelize.log(afterMsg, Date.now() - startTime, options);
const elapsedTime = Date.now() - startTime;
this.sequelize.log(`Executed ${elapsedTime > this.sequelize.options.queryThreshold ? 'SLOW QUERY ' : ''} (${instanceTypeMessage}, ${fmt}`, elapsedTime, options);
}
};
}
Expand Down
3 changes: 3 additions & 0 deletions lib/dialects/sqlite/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,9 @@ class Query extends AbstractQuery {
return new sequelizeErrors.TimeoutError(err);

default:
if (err.message && err.message.includes("SQLITE_ERROR: no such table")) {
console.log(err.sql);
}
return new sequelizeErrors.DatabaseError(err);
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ const hookTypes = {
beforeBulkSync: { params: 1 },
afterBulkSync: { params: 1 },
beforeQuery: { params: 2 },
afterQuery: { params: 2 }
afterQuery: { params: 2 },
beforeGetConnection: {params: 2}
};
exports.hooks = hookTypes;

Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2703,7 +2703,7 @@ class Model {
// Get primary keys for postgres to enable updateOnDuplicate
options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value();
if (Object.keys(model.uniqueKeys).length > 0) {
options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map('column').value();
options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map(c => c.fields[0]).value();
}
}

Expand Down
10 changes: 7 additions & 3 deletions lib/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,9 +635,13 @@ class Sequelize {

checkTransaction();

return options.transaction
? options.transaction.connection
: this.connectionManager.getConnection(options);
return (options.transaction ?
options.transaction.connection :
this.runHooks('beforeGetConnection', options, sql).then(() => this.connectionManager.getConnection(options)));

// return options.transaction
// ? options.transaction.connection
// : this.connectionManager.getConnection(options);
}).then(connection => {
const query = new this.dialect.Query(connection, this, options);
return this.runHooks('beforeQuery', options, query)
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"release": {
"branch": "v5",
"verifyConditions": [
"@semantic-release/npm",
"@semantic-release/github"
]
},
"publishConfig": {
"tag": "latest"
},
"scripts": {
"lint": "eslint lib test --quiet",
"lint-docs": "markdownlint docs",
Expand Down
54 changes: 54 additions & 0 deletions test/integration/associations/belongs-to-many.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,60 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
});
});

describe('hasAssociations with binary key', () => {
beforeEach(function() {
const keyDataType = dialect === 'mysql' || dialect === 'mariadb' ? 'BINARY(255)' : DataTypes.BLOB('tiny');
this.Article = this.sequelize.define('Article', {
id: {
type: keyDataType,
primaryKey: true
}
});
this.Label = this.sequelize.define('Label', {
id: {
type: keyDataType,
primaryKey: true
}
});
this.ArticleLabel = this.sequelize.define('ArticleLabel');

this.Article.belongsToMany(this.Label, { through: this.ArticleLabel });
this.Label.belongsToMany(this.Article, { through: this.ArticleLabel });

return this.sequelize.sync({ force: true });
});

it('answers true for labels that have been assigned', function() {
return Promise.all([
this.Article.create({
id: Buffer.alloc(255)
}),
this.Label.create({
id: Buffer.alloc(255)
})
]).then(([article, label]) => Promise.all([
article,
label,
article.addLabel(label, {
through: 'ArticleLabel'
})
])).then(([article, label]) => article.hasLabels([label]))
.then(result => expect(result).to.be.true);
});

it('answer false for labels that have not been assigned', function() {
return Promise.all([
this.Article.create({
id: Buffer.alloc(255)
}),
this.Label.create({
id: Buffer.alloc(255)
})
]).then(([article, label]) => article.hasLabels([label]))
.then(result => expect(result).to.be.false);
});
});

describe('countAssociations', () => {
beforeEach(function() {
this.User = this.sequelize.define('User', {
Expand Down
45 changes: 45 additions & 0 deletions test/integration/model/bulk-create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,53 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
});
});

it('when the primary key column names and model field names are different and have unique constraints', function() {
const Person = this.sequelize.define('Person', {
emailAddress: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
unique: true,
field: 'email_address'
},
name: {
type: DataTypes.STRING,
allowNull: false,
field: 'name'
}
}, {});

return Person.sync({ force: true })
.then(() => {
const inserts = [
{ emailAddress: 'a@example.com', name: 'Alice' }
];
return Person.bulkCreate(inserts);
})
.then(people => {
expect(people.length).to.equal(1);
expect(people[0].emailAddress).to.equal('a@example.com');
expect(people[0].name).to.equal('Alice');

const updates = [
{ emailAddress: 'a@example.com', name: 'CHANGED NAME' },
{ emailAddress: 'b@example.com', name: 'Bob' }
];

return Person.bulkCreate(updates, { updateOnDuplicate: ['emailAddress', 'name'] });
})
.then(people => {
expect(people.length).to.equal(2);
expect(people[0].emailAddress).to.equal('a@example.com');
expect(people[0].name).to.equal('CHANGED NAME');
expect(people[1].emailAddress).to.equal('b@example.com');
expect(people[1].name).to.equal('Bob');
});
});
});


it('should reject for non array updateOnDuplicate option', function() {
const data = [
{ uniqueName: 'Peter', secretValue: '42' },
Expand Down
8 changes: 5 additions & 3 deletions types/lib/model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Op = require('./operators');
import { Promise } from './promise';
import { QueryOptions, IndexesOptions } from './query-interface';
import { Config, Options, Sequelize, SyncOptions } from './sequelize';
import { Transaction } from './transaction';
import { Transaction, LOCK } from './transaction';
import { Col, Fn, Literal, Where } from './utils';
import { IndexHints } from '..';

Expand Down Expand Up @@ -548,8 +548,10 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para
* Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model
* locks with joins. See [transaction.LOCK for an example](transaction#lock)
*/
lock?: Transaction.LOCK | { level: Transaction.LOCK; of: typeof Model };

lock?:
| LOCK
| { level: LOCK; of: typeof Model }
| boolean;
/**
* Skip locked rows. Only supported in Postgres.
*/
Expand Down
109 changes: 64 additions & 45 deletions types/lib/transaction.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export class Transaction {
* Adds hook that is run after a transaction is committed
*/
public afterCommit(fn: (transaction: this) => void | Promise<void>): void;

/**
* Returns possible options for row locking
*/
static get LOCK(): LOCK;

/**
* Same as its static version, but can also be called on instances of
* transactions to get possible options for row locking directly from the
* instance.
*/
get LOCK(): LOCK;
}

// tslint:disable-next-line no-namespace
Expand Down Expand Up @@ -71,54 +83,61 @@ export namespace Transaction {
IMMEDIATE = 'IMMEDIATE',
EXCLUSIVE = 'EXCLUSIVE',
}
}

/**
* Possible options for row locking. Used in conjunction with `find` calls:
*
* ```js
* t1 // is a transaction
* t1.LOCK.UPDATE,
* t1.LOCK.SHARE,
* t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
* t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only
* ```
*
* Usage:
* ```js
* t1 // is a transaction
* Model.findAll({
* where: ...,
* transaction: t1,
* lock: t1.LOCK...
* });
* ```
*
* Postgres also supports specific locks while eager loading by using OF:
* ```js
* UserModel.findAll({
* where: ...,
* include: [TaskModel, ...],
* transaction: t1,
* lock: {
* level: t1.LOCK...,
* of: UserModel
* }
* });
* ```
* UserModel will be locked but TaskModel won't!
*/
export enum LOCK {
UPDATE = 'UPDATE',
SHARE = 'SHARE',
/**
* Possible options for row locking. Used in conjunction with `find` calls:
*
* ```js
* t1 // is a transaction
* t1.LOCK.UPDATE,
* t1.LOCK.SHARE,
* t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
* t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only
* ```
*
* Usage:
* ```js
* t1 // is a transaction
* Model.findAll({
* where: ...,
* transaction: t1,
* lock: t1.LOCK...
* });
* ```
*
* Postgres also supports specific locks while eager loading by using OF:
* ```js
* UserModel.findAll({
* where: ...,
* include: [TaskModel, ...],
* transaction: t1,
* lock: {
* level: t1.LOCK...,
* of: UserModel
* }
* });
* ```
* UserModel will be locked but TaskModel won't!
* Postgres 9.3+ only
*/
enum LOCK {
UPDATE = 'UPDATE',
SHARE = 'SHARE',
/**
* Postgres 9.3+ only
*/
KEY_SHARE = 'KEY SHARE',
/**
* Postgres 9.3+ only
*/
NO_KEY_UPDATE = 'NO KEY UPDATE',
}
KEY_SHARE = 'KEY SHARE',
/**
* Postgres 9.3+ only
*/
NO_KEY_UPDATE = 'NO KEY UPDATE',
}

interface LOCK {
UPDATE: LOCK.UPDATE;
SHARE: LOCK.SHARE;
KEY_SHARE: LOCK.KEY_SHARE;
NO_KEY_UPDATE: LOCK.NO_KEY_UPDATE;
}

/**
Expand Down
Loading