Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SEMVER-MAJOR] Disallow bulk updateOrCreate. #889

Merged
merged 1 commit into from
Aug 5, 2016
Merged

Conversation

richardpringle
Copy link

@richardpringle richardpringle commented Apr 5, 2016

BREAKING CHANGE

@bajtos, please review.

connect to strongloop/loopback#2123

@richardpringle
Copy link
Author

Also, let me know if you want me to break this up into separate commits for some of that test functionality.

if (Array.isArray(data)) {
var isBulkCreate = data.every(function(item){
var id = getIdValue(self, item);
return id === undefined || id === null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be simplified to:

return !getIdValue(self, item);

@superkhau
Copy link
Contributor

@richardpringle Reviewed with some feedback for you.

@richardpringle
Copy link
Author

fix for #2123

if (isBulkCreate) {
return self.create(data, options, cb);
} else {
return cb(new Error('updateOrCreate does not support bulk mode or any array input'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or any array input - that's not correct, is it? We still support array input when items don't contain ids.

@bajtos
Copy link
Member

bajtos commented Apr 6, 2016

fix for #2123

@richardpringle ^^^ that did not create cross-link, did you mean strongloop/loopback#2123? It's also best to put cross-links into PR description at the top (you can edit the description after the PR was created).

@superkhau @raymondfeng This change modifies the outcome of certain edge cases, e.g. when calling updateOrCreate with an array of instances where some of them have id set. See the discussion in the linked issue for more details. I think these edge cases are not valid usage of the API , thus one can argue it's not a breaking change and it's ok to back-port this change to 2.x too.

OTOH, if you think we should be more conservative and do not back-port this change, then I would vote for a more aggressive cleanup and always return cb(new Error(...)) when the input is an array.

Or perhaps we can always return an error for array input in 3.x, and in 2.x branch, we can do what's implemented here, i.e. fail only when the request is not a clear bulk create.

Thoughts?

@bajtos
Copy link
Member

bajtos commented Apr 6, 2016

Cross-posting from strongloop/loopback#2123

It makes me wonder, what happens when calling bulk-create remotely via POST method and some of the instances cannot be created, e.g. because they don't pass validation or perhaps violate unique constraint/index? Will the user receive unknown error too?

They get the same "unknown error" because of the rest-adapter error handler. The err object in this case is really an array of errors so err.message is undefined.

In that case, I am leaning towards disabling array input for updateOrCreate completely and land this change in master (3.0) only.

Thoughts?

@richardpringle
Copy link
Author

@superkhau suggested that we could also just make a change in the rest-adapter error handler to enable handling an array of errors.

I really do think it would be better to clean up both POST (create) and PUT (replaceOrCreate) and PATCH (updateOrCreate) and release the changes all at the same time to stay consistent, assuming that replaceOrCreate will run into the same issues.

So for now, I would suggest making the changes in strong-remoting, making them available in LoopBack 2.x and only landing some of my test cases here once I clean them up.

@richardpringle
Copy link
Author

Another argument for adjusting strong-remoting is that I would expect the error you get via REST would be the same as the error on the back-end.

@richardpringle
Copy link
Author

@bajtos, @superkhau, ready for another round of review

@superkhau
Copy link
Contributor

LGTM

Todo = db.define('Todo', {
content: String,
});
done();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should still call db.automigrate(done) to ensure the tables are created? Note that these tests are run by all connectors, and especially SQL connectors need to create tables before you can store any data.

The way how the code is written now, you are relying on some other tests running earlier to create a table for Todo model, possibly with a different set of properties than your Todo model has.

@bajtos
Copy link
Member

bajtos commented May 6, 2016

LGTM, please wait to see dependant build of connectors to pass, just to be sure.

@richardpringle
Copy link
Author

@slnode test please

@richardpringle
Copy link
Author

richardpringle commented May 16, 2016

dependant loopback-connector-remote is failing on all instances of node 0.10

I believe it's because of the following PR: strongloop/loopback-connector-remote#44

and more particularly https://github.com/strongloop/loopback-connector-remote/pull/44/files#r61875179

@richardpringle
Copy link
Author

Need to re-trigger tests again when strongloop/loopback-connector-remote#47 lands

@bajtos
Copy link
Member

bajtos commented May 24, 2016

@slnode test please

@richardpringle
Copy link
Author

@bajtos, it looks like the expected behavior has now changed in some dependent modules (loopback-sdk-angular for example).

What's the next step, is it to go through and re-write and/or remove unit tests from the dependants?

@bajtos
Copy link
Member

bajtos commented May 25, 2016

it looks like the expected behavior has now changed in some dependent modules (loopback-sdk-angular for example).

Ha, that just confirms that this patch is a breaking change.

What's the next step, is it to go through and re-write and/or remove unit tests from the dependants?

Could you please point me to the failing tests? Ideally, we should fix the tests to work with both LB 2.x and 3.x, but this really depends.

@richardpringle
Copy link
Author

richardpringle commented May 25, 2016

Here are the failing tests:

And below are the logs from Jenkins (link to see console on Jenkins):

Tue, 24 May 2016 12:10:35 GMT engine:ws received "42["info",{"log":"'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']","type":"log"}]"
Tue, 24 May 2016 12:10:35 GMT express:router dispatching POST /setup
Tue, 24 May 2016 12:10:35 GMT engine:socket packet
Tue, 24 May 2016 12:10:35 GMT express:router query  : /setup
Tue, 24 May 2016 12:10:35 GMT express:router expressInit  : /setup
Tue, 24 May 2016 12:10:35 GMT socket.io-parser decoded 2["info",{"log":"'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']","type":"log"}] as {"type":2,"nsp":"/","data":["info",{"log":"'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']","type":"log"}]}
Tue, 24 May 2016 12:10:35 GMT express:router <anonymous>  : /setup
Tue, 24 May 2016 12:10:35 GMT express:router jsonParser  : /setup
Tue, 24 May 2016 12:10:35 GMT socket.io:socket got packet {"type":2,"nsp":"/","data":["info",{"log":"'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']","type":"log"}]}
Tue, 24 May 2016 12:10:35 GMT body-parser:json content-type "application/json"
Tue, 24 May 2016 12:10:35 GMT socket.io:socket emitting event ["info",{"log":"'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']","type":"log"}]
Tue, 24 May 2016 12:10:35 GMT body-parser:json content-encoding "identity"
Tue, 24 May 2016 12:10:35 GMT body-parser:json read body
LOG: 'methods', ['get', 'save', 'query', 'remove', 'delete', 'create', 'upsert', 'exists', 'findById', 'find', 'findOne', 'updateAll', 'deleteById', 'count', 'prototype$updateAttributes', 'createChangeStream', 'bind', 'updateOrCreate', 'update', 'destroyById', 'removeById', 'modelName']
Tue, 24 May 2016 12:10:35 GMT engine:ws received "42["result",{"id":"","description":"has all methods including aliases","suite":["services","MyModel $resource"],"success":false,"skipped":false,"time":5,"log":["expected [ Array(22) ] to be a superset of [ Array(13) ]\nassert@http://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:873:67\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:2293:25\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:3627:30\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:399:47"],"assertionErrors":[{"name":"AssertionError","message":"expected [ Array(22) ] to be a superset of [ Array(13) ]","showDiff":false}]}]"
Tue, 24 May 2016 12:10:35 GMT engine:socket packet
Tue, 24 May 2016 12:10:35 GMT socket.io-parser decoded 2["result",{"id":"","description":"has all methods including aliases","suite":["services","MyModel $resource"],"success":false,"skipped":false,"time":5,"log":["expected [ Array(22) ] to be a superset of [ Array(13) ]\nassert@http://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:873:67\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:2293:25\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:3627:30\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:399:47"],"assertionErrors":[{"name":"AssertionError","message":"expected [ Array(22) ] to be a superset of [ Array(13) ]","showDiff":false}]}] as {"type":2,"nsp":"/","data":["result",{"id":"","description":"has all methods including aliases","suite":["services","MyModel $resource"],"success":false,"skipped":false,"time":5,"log":["expected [ Array(22) ] to be a superset of [ Array(13) ]\nassert@http://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:873:67\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:2293:25\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:3627:30\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:399:47"],"assertionErrors":[{"name":"AssertionError","message":"expected [ Array(22) ] to be a superset of [ Array(13) ]","showDiff":false}]}]}
Tue, 24 May 2016 12:10:35 GMT socket.io:socket got packet {"type":2,"nsp":"/","data":["result",{"id":"","description":"has all methods including aliases","suite":["services","MyModel $resource"],"success":false,"skipped":false,"time":5,"log":["expected [ Array(22) ] to be a superset of [ Array(13) ]\nassert@http://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:873:67\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:2293:25\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:3627:30\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:399:47"],"assertionErrors":[{"name":"AssertionError","message":"expected [ Array(22) ] to be a superset of [ Array(13) ]","showDiff":false}]}]}
Tue, 24 May 2016 12:10:35 GMT socket.io:socket emitting event ["result",{"id":"","description":"has all methods including aliases","suite":["services","MyModel $resource"],"success":false,"skipped":false,"time":5,"log":["expected [ Array(22) ] to be a superset of [ Array(13) ]\nassert@http://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:873:67\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:2293:25\nhttp://localhost:9876/base/node_modules/chai/chai.js?80a81da918cc1a355fd23c104379920817ef5b78:3627:30\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:399:47"],"assertionErrors":[{"name":"AssertionError","message":"expected [ Array(22) ] to be a superset of [ Array(13) ]","showDiff":false}]}]
      ✖ has all methods including aliases
Tue, 24 May 2016 12:10:37 GMT socket.io-parser decoded 2["result",{"id":"","description":"creates multiple related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":false,"time":0,"log":["undefined is not an object (evaluating 'action.apply')\ncreateMany@http://localhost:3838/services?services::with%20authentication%20using%20built-in%20User%20model::adds%20auth-related%20methods%20to%20the%20User%20model:913:24\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:936:49"],"assertionErrors":[]}] as {"type":2,"nsp":"/","data":["result",{"id":"","description":"creates multiple related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":false,"time":0,"log":["undefined is not an object (evaluating 'action.apply')\ncreateMany@http://localhost:3838/services?services::with%20authentication%20using%20built-in%20User%20model::adds%20auth-related%20methods%20to%20the%20User%20model:913:24\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:936:49"],"assertionErrors":[]}]}
Tue, 24 May 2016 12:10:37 GMT express:router <anonymous>  : /setup
Tue, 24 May 2016 12:10:37 GMT socket.io:socket got packet {"type":2,"nsp":"/","data":["result",{"id":"","description":"creates multiple related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":false,"time":0,"log":["undefined is not an object (evaluating 'action.apply')\ncreateMany@http://localhost:3838/services?services::with%20authentication%20using%20built-in%20User%20model::adds%20auth-related%20methods%20to%20the%20User%20model:913:24\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:936:49"],"assertionErrors":[]}]}
Tue, 24 May 2016 12:10:37 GMT express:router jsonParser  : /setup
Tue, 24 May 2016 12:10:37 GMT body-parser:json content-type "application/json"
Tue, 24 May 2016 12:10:37 GMT socket.io:socket emitting event ["result",{"id":"","description":"creates multiple related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":false,"time":0,"log":["undefined is not an object (evaluating 'action.apply')\ncreateMany@http://localhost:3838/services?services::with%20authentication%20using%20built-in%20User%20model::adds%20auth-related%20methods%20to%20the%20User%20model:913:24\nhttp://localhost:9876/base/test.e2e/spec/services.spec.js?e850318c0e078721d0c01502db8db3ccb18edce7:936:49"],"assertionErrors":[]}]
Tue, 24 May 2016 12:10:37 GMT body-parser:json content-encoding "identity"
Tue, 24 May 2016 12:10:37 GMT body-parser:json read body
      ✖ creates multiple related models
Tue, 24 May 2016 12:10:37 GMT engine:ws received "42["result",{"id":"","description":"removes all related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":true,"time":0,"log":[],"assertionErrors":[]}]"
Tue, 24 May 2016 12:10:37 GMT engine:socket packet
Tue, 24 May 2016 12:10:37 GMT socket.io-parser decoded 2["result",{"id":"","description":"removes all related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":true,"time":0,"log":[],"assertionErrors":[]}] as {"type":2,"nsp":"/","data":["result",{"id":"","description":"removes all related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":true,"time":0,"log":[],"assertionErrors":[]}]}
Tue, 24 May 2016 12:10:37 GMT socket.io:socket got packet {"type":2,"nsp":"/","data":["result",{"id":"","description":"removes all related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":true,"time":0,"log":[],"assertionErrors":[]}]}
Tue, 24 May 2016 12:10:37 GMT socket.io:socket emitting event ["result",{"id":"","description":"removes all related models","suite":["services","for models with hasAndBelongsToMany relations"],"success":false,"skipped":true,"time":0,"log":[],"assertionErrors":[]}]
      ✖ removes all related models (skipped)
FAILED TESTS:
    MyModel $resource
      ✖ can create multiple new resources
        PhantomJS 2.1.1 (Linux 0.0.0)
      undefined is not a function (evaluating 'MyModel.createMany')
      /mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/test.e2e/spec/services.spec.js:331:37

      ✖ has all methods including aliases
        PhantomJS 2.1.1 (Linux 0.0.0)
      expected [ Array(22) ] to be a superset of [ Array(13) ]
      assert@/mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/node_modules/chai/chai.js:873:67
      /mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/node_modules/chai/chai.js:2293:25
      /mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/node_modules/chai/chai.js:3627:30
      /mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/test.e2e/spec/services.spec.js:399:47

      ✖ creates multiple related models
        PhantomJS 2.1.1 (Linux 0.0.0)
      undefined is not an object (evaluating 'action.apply')
      createMany@http://localhost:3838/services?services::with%20authentication%20using%20built-in%20User%20model::adds%20auth-related%20methods%20to%20the%20User%20model:913:24
      /mnt/jenkins/workspace/loopback-sdk-angular/89d66e82/test.e2e/spec/services.spec.js:936:49

@richardpringle
Copy link
Author

@slnode test please

@bajtos bajtos self-assigned this Jun 14, 2016
@bajtos
Copy link
Member

bajtos commented Aug 4, 2016

@richardpringle what's the status of this patch?

@bajtos
Copy link
Member

bajtos commented Aug 4, 2016

@richardpringle I have rebased the patch on the current master and resolved merge conflicts. Run git fetch && git reset --hard origin/PUT-bulk-update to update your local branch.

@bajtos
Copy link
Member

bajtos commented Aug 5, 2016

AFAICT, the downstream CI failures are not related to this change. I am going to land it.

@bajtos bajtos merged commit e3b6b78 into master Aug 5, 2016
@bajtos bajtos deleted the PUT-bulk-update branch August 5, 2016 11:33
@bajtos bajtos removed the #review label Aug 5, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants