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
feature: Improved Data Type validation/conversion. #677
base: master
Are you sure you want to change the base?
Conversation
src/data-types/timen.js
Outdated
return value; | ||
dateValue = value; | ||
} else { | ||
dateValue = new Date(Date.parse(value)); |
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.
Need to remove this redundant conversion to Date too 😄
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.
Yeah. I'm just keeping it because the old code allowed passing in strings as values for Time
. We can remove this in a breaking change later. 👍
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.
I brought that up because datetime2n and datetimeoffsetn had conversion removed. Shouldn't the same behaviour apply for all 3 datatype?
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.
Yeah, unfortunately I think that there might be users out there that use time
and pass in timestrings. I'm not 100% sure, and I'd rather not break this right now, but sometime in the future. 😅
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.
😅 I think I should elaborated a bit more. If users are passing time
in timestrings format, then it is likely they will do the same for datetime2
and datetimeoffset
, as they all have nano second precision.
Code fragment from timen.js and datetimeoffsetn.js looks like,
validate(value) {
...
dateValue = new Date(Date.parse(value));
...
}
writeParameterData: function(..){
..
if (parameter.value != null) {
const time = new Date(+parameter.value); // datetime2n.js doesn't have this conversion
...
}
We should add the conversion(pointed in the code fragment) back into datetime2n.js to allow string passed into datetime2
.
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.
Ah, yes, you're totally right! ❤️ Fixed this in 0d4e197.
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.
I think all 3 types now use Date.parse
in the validate
method to allow conversion from strings, and they no longer perform redundant conversion inside the writeParameterData
method.
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.
sweet 👍
src/data-types/money.js
Outdated
return value; | ||
|
||
if (!Number.isFinite(numberValue) || (typeof value === 'string' && value !== numberValue.toString()) || numberValue.toString() !== numberValue.toFixed(4)) { | ||
return new TypeError(`The given value could not be converted to ${this.name}`); |
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.
How about removing or having an alternative for numberValue.toString() !== numberValue.toFixed(4)
?
This condition is too strict, if input has scale < 4 it will throw exception, but driver should allow scale <= 4. Same goes for smallmoney.
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 also add a test for money/smallmoney type with scale < 4? All current tests are for scale 4.
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.
You're right, I missed handling of numbers that have less than 4 numbers after the decimal point. 😓
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.
numberValue.toString().split('.')[1].length > 4
src/data-types/nvarchar.js
Outdated
validate: function(value) { | ||
if (value == null) { | ||
validate(value, length, precision, scale) { | ||
if (value === undefined || value === null) { |
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.
Is precision & scale here by mistake?
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.
Yeah, will drop those! 👍
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.
Done in f9b49e1.
src/data-types/real.js
Outdated
return value; | ||
|
||
if (!Number.isFinite(numberValue) || (typeof value === 'string' && value !== numberValue.toString()) || numberValue !== Math.fround(numberValue)) { | ||
return new TypeError(`The given value could not be converted to ${this.name}`); |
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.
Wouldn't using Math.fround() be a breaking change? 🤔 I'm afraid that most of the data passed would fail at this point, and hinder the usage of real
type.
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.
Yeah, this one's a bit harder to decide.
The loss of precision is a huge issue tho, because most users will not know that real
is limited and won't save the exact value they're trying to send, and thus the data in the database can no longer be trusted. That's why I think raising an error here is better than before and fixes a bug. What do you think? 😄
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.
I agree to raising error here ✌️ I was just wondering how much of an impact it would have on existing user(i.e. will it be a breaking change), but as you pointed, users must have already run into precision loss, so it better to have this in sooner rather than later.
src/data-types/varchar.js
Outdated
return TypeError('Invalid string.'); | ||
} | ||
value = value.toString(); | ||
|
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.
suggestion: remove precision, scale
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.
Good idea! 👍
src/request.js
Outdated
@@ -175,11 +175,13 @@ module.exports = class Request extends EventEmitter { | |||
validateParameters() { | |||
for (let i = 0, len = this.parameters.length; i < len; i++) { | |||
const parameter = this.parameters[i]; | |||
const value = parameter.type.validate(parameter.value); | |||
const value = parameter.type.validate(parameter.value, parameter.length, parameter.precision, parameter.scale); |
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.
Precision and scale are never actually used the validate(), let's remove them.
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.
Yeah, that's a good point. They should be used for numeric
and decimal
(and maybe for some of the date types?). I'll take another look! 👍
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.
Done in 0968cb4.
31d6acd
to
9ae5f2f
Compare
This refactors the data type logic in tedious, making it more strict and more accurate. If the parameter specified by the user can not be converted to the data type of the parameter without a loss of precision, the request will not be executed and an error will be passed to the request's callback instead. The same parameter value conversion logic is now also used for both regular requests as well as for bulk loads, fixing a long standing issue where storing the same data via these two methods would result either result in a failure or different data being stored in the database.
9ae5f2f
to
1c4d0a6
Compare
Heya @tediousjs/microsoft-contributors 👋 how do you feel about this changeset? Should we declare it as a breaking change and merge and release it together with other breaking changes as |
+1 to releasing this along with other breaking changes. This PR is good to go once #677 (review) is handled 😄 |
src/bulk-load.js
Outdated
length: c.length, | ||
scale: c.scale, | ||
precision: c.precision, | ||
value: row[arr ? i : c.objName] | ||
}, this.options); | ||
value: c.type.validate(row[arr ? i : c.objName], c.length, c.precision, c.scale) |
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.
validate()
doesn't throw error but it returns error if validation fails. Can you add check to verify type of validate()
's return value before passing it to writeParameterData
?
@arthurschreiber, I think this PR may be outdated since the recent merged pip related changes? If you can confirm that, then I think can close this PR for now. |
…ter-type-validation
src/data-types/float.js
Outdated
@@ -17,20 +17,28 @@ module.exports = { | |||
writeParameterData: function(buffer, parameter) { | |||
if (parameter.value != null) { | |||
buffer.writeUInt8(8); | |||
buffer.writeDoubleLE(parseFloat(parameter.value)); | |||
buffer.writeDoubleLE(parameter.value); |
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.
I'm not sure if this change is necessary?
src/data-types/tvp.js
Outdated
return { | ||
value: column.type.validate(columnValue, column.length, column.scale, column.precision), | ||
length: column.length, | ||
scale: column.scale, | ||
precision: column.precision | ||
}; |
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.
Returning an object causes data-type value inputs to vary: if using TVP, data-type inputs would be this object, whereas anything else would be a single value. To avoid this two variations of data-type inputs, this TVP .validate()
method should only return the single value, such as return column.type.validate(columnValue, column.length, column.scale, column.precision)
Codecov Report
@@ Coverage Diff @@
## master #677 +/- ##
==========================================
- Coverage 80.77% 79.85% -0.93%
==========================================
Files 86 86
Lines 4384 4427 +43
Branches 793 807 +14
==========================================
- Hits 3541 3535 -6
- Misses 584 630 +46
- Partials 259 262 +3
Continue to review full report at Codecov.
|
This refactors the data type logic in tedious, making it more strict and more accurate.
If the parameter specified by the user can not be converted to the data type of the parameter without a loss of precision, the request will not be executed and an error will be passed to the request's callback instead.
The same parameter value conversion logic is now also used for both regular requests as well as for bulk loads, fixing a long standing issue where storing the same data via these two methods would result either result in a failure or different data being stored in the database.
This is dependent upon #676 being merged first.