不要怂,就是干,撸起袖子干!

Commit c381625d by Sushant Committed by GitHub

Fixed: jsonb/json update or softdelete (#7499)

1 parent 18f63e3f
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
- [ADDED] Added support for associations aliases in orders and groups. [#7425](https://github.com/sequelize/sequelize/issues/7425) - [ADDED] Added support for associations aliases in orders and groups. [#7425](https://github.com/sequelize/sequelize/issues/7425)
- [REMOVED] Removes support for `{raw: 'injection goes here'}` for order and group. [#7188](https://github.com/sequelize/sequelize/issues/7188) - [REMOVED] Removes support for `{raw: 'injection goes here'}` for order and group. [#7188](https://github.com/sequelize/sequelize/issues/7188)
- [FIXED] `showIndex` breaks with newline characters [#7492](https://github.com/sequelize/sequelize/pull/7492) - [FIXED] `showIndex` breaks with newline characters [#7492](https://github.com/sequelize/sequelize/pull/7492)
- [FIXED] Update or soft delete breaks when querying on `JSON/JSONB` [#7376](https://github.com/sequelize/sequelize/issues/7376) [#7400](https://github.com/sequelize/sequelize/issues/7400) [#7444](https://github.com/sequelize/sequelize/issues/7444)
## BC breaks: ## BC breaks:
- Model.validate instance method now runs validation hooks by default. Previously you needed to pass { hooks: true }. You can override this behavior by passing { hooks: false } - Model.validate instance method now runs validation hooks by default. Previously you needed to pass { hooks: true }. You can override this behavior by passing { hooks: false }
......
...@@ -362,7 +362,7 @@ const QueryGenerator = { ...@@ -362,7 +362,7 @@ const QueryGenerator = {
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
values: values.join(','), values: values.join(','),
output: outputFragment, output: outputFragment,
where: this.whereQuery(where), where: this.whereQuery(where, options),
tmpTable tmpTable
}; };
...@@ -1930,7 +1930,7 @@ const QueryGenerator = { ...@@ -1930,7 +1930,7 @@ const QueryGenerator = {
let outerBinding; let outerBinding;
let comparator = '='; let comparator = '=';
let field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]; let field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key];
let fieldType = options.type || field && field.type; let fieldType = field && field.type || options.type;
if (key && typeof key === 'string' && key.indexOf('.') !== -1 && options.model) { if (key && typeof key === 'string' && key.indexOf('.') !== -1 && options.model) {
if (options.model.rawAttributes[key.split('.')[0]] && options.model.rawAttributes[key.split('.')[0]].type instanceof DataTypes.JSON) { if (options.model.rawAttributes[key.split('.')[0]] && options.model.rawAttributes[key.split('.')[0]].type instanceof DataTypes.JSON) {
......
...@@ -241,7 +241,7 @@ const QueryGenerator = { ...@@ -241,7 +241,7 @@ const QueryGenerator = {
values.push(this.quoteIdentifier(key) + '=' + this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })); values.push(this.quoteIdentifier(key) + '=' + this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }));
} }
return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where)}`; return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where, options)}`;
}, },
deleteQuery(tableName, where, options, model) { deleteQuery(tableName, where, options, model) {
......
...@@ -56,67 +56,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -56,67 +56,36 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
} }
it('should create an instance with JSON data', function() { describe('create', () => {
return this.Event.create({ it('should create an instance with JSON data', function() {
data: { return this.Event.create({
name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
}
}).then(() => {
return this.Event.findAll().then(events => {
const event = events[0];
expect(event.get('data')).to.eql({
name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
});
});
});
});
it('should update an instance with JSON data', function() {
return this.Event.create({
data: {
name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
}
}).then(event => {
return event.update({
data: { data: {
name: { name: {
first: 'Homer', first: 'Homer',
last: 'Simpson' last: 'Simpson'
}, },
employment: null employment: 'Nuclear Safety Inspector'
} }
}); }).then(() => {
}).then(() => { return this.Event.findAll().then(events => {
return this.Event.findAll().then(events => { const event = events[0];
const event = events[0];
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
name: { name: {
first: 'Homer', first: 'Homer',
last: 'Simpson' last: 'Simpson'
}, },
employment: null employment: 'Nuclear Safety Inspector'
});
}); });
}); });
}); });
}); });
it('should be possible to query a nested value', function() { describe('update', () => {
return Promise.join( it('should update with JSON column (dot notation)', function() {
this.Event.create({ return this.Event.bulkCreate([{
id: 1,
data: { data: {
name: { name: {
first: 'Homer', first: 'Homer',
...@@ -124,85 +93,86 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -124,85 +93,86 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
} }
}), }, {
this.Event.create({ id: 2,
data: { data: {
name: { name: {
first: 'Marge', first: 'Rick',
last: 'Simpson' last: 'Sanchez'
},
employment: 'Multiverse Scientist'
}
}]).then(() => this.Event.update({
'data': {
name: {
first: 'Rick',
last: 'Sanchez'
}, },
employment: 'Housewife' employment: 'Galactic Fed Prisioner'
} }
}) }, {
).then(() => {
return this.Event.findAll({
where: { where: {
data: { 'data.name.first': 'Rick'
employment: 'Housewife'
}
} }
}).then(events => { })).then(() => this.Event.findById(2)).then(event => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
name: { name: {
first: 'Marge', first: 'Rick',
last: 'Simpson' last: 'Sanchez'
}, },
employment: 'Housewife' employment: 'Galactic Fed Prisioner'
}); });
}); });
}); });
});
it('should be possible to query a nested integer value', function() { it('should update with JSON column (JSON notation)', function() {
return Promise.join( return this.Event.bulkCreate([{
this.Event.create({ id: 1,
data: { data: {
name: { name: {
first: 'Homer', first: 'Homer',
last: 'Simpson' last: 'Simpson'
}, },
age: 40 employment: 'Nuclear Safety Inspector'
} }
}), }, {
this.Event.create({ id: 2,
data: { data: {
name: { name: {
first: 'Marge', first: 'Rick',
last: 'Simpson' last: 'Sanchez'
}, },
age: 37 employment: 'Multiverse Scientist'
} }
}) }]).then(() => this.Event.update({
).then(() => { 'data': {
return this.Event.findAll({ name: {
first: 'Rick',
last: 'Sanchez'
},
employment: 'Galactic Fed Prisioner'
}
}, {
where: { where: {
data: { data: {
age: { name: {
$gt: 38 first: 'Rick'
} }
} }
} }
}).then(events => { })).then(() => this.Event.findById(2)).then(event => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({ expect(event.get('data')).to.eql({
name: { name: {
first: 'Homer', first: 'Rick',
last: 'Simpson' last: 'Sanchez'
}, },
age: 40 employment: 'Galactic Fed Prisioner'
}); });
}); });
}); });
});
it('should be possible to query a nested null value', function() { it('should update an instance with JSON data', function() {
return Promise.join( return this.Event.create({
this.Event.create({
data: { data: {
name: { name: {
first: 'Homer', first: 'Homer',
...@@ -210,146 +180,272 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -210,146 +180,272 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
} }
}), }).then(event => {
this.Event.create({ return event.update({
data: {
name: {
first: 'Marge',
last: 'Simpson'
},
employment: null
}
})
).then(() => {
return this.Event.findAll({
where: {
data: { data: {
name: {
first: 'Homer',
last: 'Simpson'
},
employment: null employment: null
} }
} });
}).then(events => { }).then(() => {
expect(events.length).to.equal(1); return this.Event.findAll().then(events => {
expect(events[0].get('data')).to.eql({ const event = events[0];
name: {
first: 'Marge', expect(event.get('data')).to.eql({
last: 'Simpson' name: {
}, first: 'Homer',
employment: null last: 'Simpson'
},
employment: null
});
}); });
}); });
}); });
}); });
it('should be possible to query multiple nested values', function() { describe('find', () => {
return this.Event.create({ it('should be possible to query a nested value', function() {
data: {
name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
}
}).then(() => {
return Promise.join( return Promise.join(
this.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
}
}),
this.Event.create({
data: {
name: {
first: 'Marge', first: 'Marge',
last: 'Simpson' last: 'Simpson'
}, },
employment: 'Housewife' employment: 'Housewife'
} }
})
).then(() => {
return this.Event.findAll({
where: {
data: {
employment: 'Housewife'
}
}
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({
name: {
first: 'Marge',
last: 'Simpson'
},
employment: 'Housewife'
});
});
});
});
it('should be possible to query a nested integer value', function() {
return Promise.join(
this.Event.create({
data: {
name: {
first: 'Homer',
last: 'Simpson'
},
age: 40
}
}), }),
this.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Bart', first: 'Marge',
last: 'Simpson' last: 'Simpson'
}, },
employment: 'None' age: 37
} }
}) })
); ).then(() => {
}).then(() => { return this.Event.findAll({
return this.Event.findAll({ where: {
where: { data: {
age: {
$gt: 38
}
}
}
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('data')).to.eql({
name: {
first: 'Homer',
last: 'Simpson'
},
age: 40
});
});
});
});
it('should be possible to query a nested null value', function() {
return Promise.join(
this.Event.create({
data: { data: {
name: { name: {
first: 'Homer',
last: 'Simpson' last: 'Simpson'
}, },
employment: { employment: 'Nuclear Safety Inspector'
$ne: 'None' }
}),
this.Event.create({
data: {
name: {
first: 'Marge',
last: 'Simpson'
},
employment: null
}
})
).then(() => {
return this.Event.findAll({
where: {
data: {
employment: null
} }
} }
}, }).then(events => {
order: [ expect(events.length).to.equal(1);
['id', 'ASC'] expect(events[0].get('data')).to.eql({
] name: {
}).then(events => { first: 'Marge',
expect(events.length).to.equal(2); last: 'Simpson'
},
employment: null
});
});
});
});
expect(events[0].get('data')).to.eql({ it('should be possible to query multiple nested values', function() {
return this.Event.create({
data: {
name: { name: {
first: 'Homer', first: 'Homer',
last: 'Simpson' last: 'Simpson'
}, },
employment: 'Nuclear Safety Inspector' employment: 'Nuclear Safety Inspector'
}); }
}).then(() => {
expect(events[1].get('data')).to.eql({ return Promise.join(
name: { this.Event.create({
first: 'Marge', data: {
last: 'Simpson' name: {
first: 'Marge',
last: 'Simpson'
},
employment: 'Housewife'
}
}),
this.Event.create({
data: {
name: {
first: 'Bart',
last: 'Simpson'
},
employment: 'None'
}
})
);
}).then(() => {
return this.Event.findAll({
where: {
data: {
name: {
last: 'Simpson'
},
employment: {
$ne: 'None'
}
}
}, },
employment: 'Housewife' order: [
['id', 'ASC']
]
}).then(events => {
expect(events.length).to.equal(2);
expect(events[0].get('data')).to.eql({
name: {
first: 'Homer',
last: 'Simpson'
},
employment: 'Nuclear Safety Inspector'
});
expect(events[1].get('data')).to.eql({
name: {
first: 'Marge',
last: 'Simpson'
},
employment: 'Housewife'
});
}); });
}); });
}); });
}); });
it('should be possible to destroy with where', function() { describe('destroy', () => {
const conditionSearch = { it('should be possible to destroy with where', function() {
where: { const conditionSearch = {
data: { where: {
employment: 'Hacker' data: {
employment: 'Hacker'
}
} }
} };
};
return Promise.join( return Promise.join(
this.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Elliot', first: 'Elliot',
last: 'Alderson' last: 'Alderson'
}, },
employment: 'Hacker' employment: 'Hacker'
} }
}), }),
this.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: 'Christian', first: 'Christian',
last: 'Slater' last: 'Slater'
}, },
employment: 'Hacker' employment: 'Hacker'
} }
}), }),
this.Event.create({ this.Event.create({
data: { data: {
name: { name: {
first: ' Tyrell', first: ' Tyrell',
last: 'Wellick' last: 'Wellick'
}, },
employment: 'CTO' employment: 'CTO'
} }
}) })
).then(() => { ).then(() => {
return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2);
}).then(() => { }).then(() => {
return this.Event.destroy(conditionSearch); return this.Event.destroy(conditionSearch);
}).then(() => { }).then(() => {
return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0);
});
}); });
}); });
......
...@@ -5,6 +5,7 @@ const DataTypes = require(__dirname + '/../../../lib/data-types'); ...@@ -5,6 +5,7 @@ const DataTypes = require(__dirname + '/../../../lib/data-types');
const chai = require('chai'); const chai = require('chai');
const expect = chai.expect; const expect = chai.expect;
const sinon = require('sinon'); const sinon = require('sinon');
const current = Support.sequelize;
describe(Support.getTestDialectTeaser('Model'), () => { describe(Support.getTestDialectTeaser('Model'), () => {
describe('paranoid', () => { describe('paranoid', () => {
...@@ -100,5 +101,62 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -100,5 +101,62 @@ describe(Support.getTestDialectTeaser('Model'), () => {
expect(count).to.be.equal(1); expect(count).to.be.equal(1);
}); });
}); });
if (current.dialect.supports.JSON) {
describe('JSONB', () => {
before(function() {
this.Model = this.sequelize.define('Model', {
name: {
type: DataTypes.STRING
},
data: {
type: DataTypes.JSONB
},
deletedAt: {
type: DataTypes.DATE,
allowNull: true,
field: 'deleted_at'
}
}, {
paranoid: true,
timestamps: true,
deletedAt: 'deletedAt'
});
});
beforeEach(function() {
return this.Model.sync({ force: true });
});
it('should soft delete with JSONB condition', function() {
return this.Model.bulkCreate([{
name: 'One',
data: {
field: {
deep: true
}
}
}, {
name: 'Two',
data: {
field: {
deep: false
}
}
}]).then(() => this.Model.destroy({
where: {
data: {
field: {
deep: true
}
}
}
})).then(() => this.Model.findAll()).then(records => {
expect(records.length).to.equal(1);
expect(records[0].get('name')).to.equal('Two');
});
});
});
}
}); });
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!