-
Notifications
You must be signed in to change notification settings - Fork 181
Fix serialization of arrays of string in update #428
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
Fix serialization of arrays of string in update #428
Conversation
Can one of the admins verify this patch? To accept patch and trigger a build add comment ".ok\W+to\W+test." |
@slnode test please |
Ok so I've seen the test that failed was that in the mean time my branch got behind master... I'll try to rebase it as soon as possible. |
ac706b9
to
dc47d13
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onaluf Thank you for creating the PR! I left a few comments for refactoring the tests.
I need a bit more time to setup a postgresql server for testing, will review the code change asap
test/postgresql.test.js
Outdated
}, | ||
function(callback) { | ||
Model.findOne({where: {id: 1}}, function(err, r) { | ||
r.should.have.property('productCodes'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please also add a check for productCodes
's value:
should.equal(r.productCodes, ['AA', 'AC']);
test/postgresql.test.js
Outdated
'id': { | ||
'type': 'number', 'id': true, 'generated': true, | ||
}, | ||
'productCodes': { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add productCodes
as a property of the existing model Post
?
See its definition in https://github.com/strongloop/loopback-connector-postgresql/blob/dc47d13d5e15d5c31d75fb7c044d2e97542072df/test/postgresql.test.js#L61-L66
And you can add the tests after this one(it also tests patch):
https://github.com/strongloop/loopback-connector-postgresql/blob/dc47d13d5e15d5c31d75fb7c044d2e97542072df/test/postgresql.test.js#L193
test/postgresql.test.js
Outdated
Model.destroyAll(callback); | ||
}, | ||
function(callback) { | ||
Model.create({productCodes: ['AA', 'AB']}, callback); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We support the promise chain of CRUD operations, example see
https://github.com/strongloop/loopback-datasource-juggler/blob/master/test/crud-with-options.test.js#L149
So you can write the chain calls as
Post.create(params)
.then(()=> {
return Post.updateAll(params)
})
.then(()=> {
// other function calls
})
Hello, guys. This solution doesn't work, because it's not a real postgresql array type in database. It will be convert just as text field type. You can easily check this, if you add postgresql settings in productCodes field
After that the code of author will fail with error from pg: malformed array literal: ""["AA","AB"]"", because postgresql array type expects {'AA', 'AB'}. @jannyHou FYI ^^ I'm going to create small app to reproduce this issue. I'll write here about it |
dc47d13
to
2e7a86d
Compare
@jannyHou I've changed the test according to your pointers, hope it suits you. @spirAde yes the array is saved as a string in the DB but it's then deserialized correctly. I think this is the correct behavior, at least that's the current way the connector behaves when creating the entry in the first place.... only the updating is problematic as far as I can tell. |
@spirAde Sorry I think I now get what you mean... if the dataType is explicitly set to |
2e7a86d
to
e118c49
Compare
Ok @spirAde, @jannyHou I think this time I've got it. I've written the test for both cases and generalized the solution for non-string array. The issue, as far as I understand it, was that
This is why I could create but not update my model AND people in #342 could update but not create theirs. I hope the proposed solution is suitable but don't hesitate and I'll continue to improve it if needed. |
lib/postgresql.js
Outdated
const isArrayDataType = prop.postgresql && | ||
prop.postgresql.dataType && | ||
(prop.postgresql.dataType.indexOf('[]') > -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There may be a more elegant way to detect this but I couldn't find one...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onaluf is varchar[]
the only possible data type? or datatype in format *[]
are all valid conditions?
If varchar[]
is the only valid one, you can use
const isArrayDataType = prop.postgresql &&
prop.postgresql.dataType === 'varchar[]';
instead.
@onaluf thank you, it works now. It's awesome. I'm not sure about serialization of array and convert it to database text column type. From my perspective, loopback should supports the strict postgresql array type only, it means you always should pass postgresql dataType varchar[]. If user wants to store serialized array in text field in database, this should remain on the user side. In this case you can simplify your if-else conditions
But only maintainers can answer on this question. Anyway, @onaluf thank you again, best wishes and have a good day |
According to function buildColumnType, this is the expected behavior. So the array type data is serialized to a string then deserialized to the array. I think the reason is SQL databases have weaker support for object, and the DSL of LB is inspired by mongodb...So type like array and object are serialized. |
test/postgresql.test.js
Outdated
post.tags[1].should.eql('AB'); | ||
return Post.updateAll({where: {id: postId}}, {tags: ['AA', 'AC']}); | ||
}) | ||
.then((wooot)=> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
: -pwooot
test/postgresql.test.js
Outdated
post.categories[1].should.eql('AB'); | ||
return Post.updateAll({where: {id: postId}}, {categories: ['AA', 'AC']}); | ||
}) | ||
.then((wooot)=> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, leaving it empty is ok :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/postgresql.js
Outdated
const isArrayDataType = prop.postgresql && | ||
prop.postgresql.dataType && | ||
(prop.postgresql.dataType.indexOf('[]') > -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onaluf is varchar[]
the only possible data type? or datatype in format *[]
are all valid conditions?
If varchar[]
is the only valid one, you can use
const isArrayDataType = prop.postgresql &&
prop.postgresql.dataType === 'varchar[]';
instead.
lib/postgresql.js
Outdated
@@ -766,6 +766,26 @@ PostgreSQL.prototype.toColumnValue = function(prop, val, isWhereClause) { | |||
}); | |||
} | |||
|
|||
if (prop.type instanceof Array) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Array.isArray(prop.type)
is better
lib/postgresql.js
Outdated
const isArrayDataType = prop.postgresql && | ||
prop.postgresql.dataType && | ||
(prop.postgresql.dataType.indexOf('[]') > -1); | ||
if (val instanceof Array) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto Array.isArray(val)
is better.
@slnode test please |
@jannyHou Thanks for the constructive feedback! I think I've solved all the point you raised. |
@slnode test please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 @onaluf Thank you and cheers that all tests pass! The code LGTM.
@onaluf Almost there, could you squash the 2 commits into 1? Then we will help you land it! |
f5d80d6
to
f98144a
Compare
Done! |
@slnode test please |
1 similar comment
@slnode test please |
Merged 🎉 Thanks everyone again for the contribution and feedback! |
Signed-off-by: SAMI BETTAYEB <sami3639@gmail.com>
* Fix serialization of arrays of string in update (loopbackio#428) (Selim Arsever) Signed-off-by: SAMI BETTAYEB <sami3639@gmail.com>
This pull request fixes a bug I encountered with the PostgeSQL connector when updating the value of a model's property of type array of strings.
When creating the model no issue occurs but when updating it the value stored in the column turns into an invalid array, for example '{ "A","B","C"}' instead of '["A","B","C"]. After a bit of investigating I thought it was linked to the juggler giving the value in the query as an Array instead of a List object but even after fixing this the issue was still there. So I figured that it must be in the connector and changed it there. (This may be related to #342)
If you think this change belong somewhere else just tell me and I'll move it.
The added unit test just check that the object is still deserialisable after an update. It fails without the change and pass with it.
Checklist
👉 Read and sign the CLA (Contributor License Agreement) 👈 (done)
npm test
passes on your machine