Skip to content

Commit

Permalink
feat: improved data type conversion
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
arthurschreiber committed Jan 30, 2018
1 parent 9485048 commit 9ae5f2f
Show file tree
Hide file tree
Showing 31 changed files with 398 additions and 279 deletions.
9 changes: 6 additions & 3 deletions src/bulk-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,15 @@ module.exports = class BulkLoad extends EventEmitter {
const arr = row instanceof Array;
for (let i = 0, len = this.columns.length; i < len; i++) {
const c = this.columns[i];
c.type.writeParameterData(this.rowsData, {

const parameter = {
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)
};

c.type.writeParameterData(this.rowsData, parameter, this.options);
}
}

Expand Down
32 changes: 13 additions & 19 deletions src/data-types/bigint.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,25 @@ module.exports = {
},

writeParameterData: function(buffer, parameter) {
if (parameter.value != null) {
const val = typeof parameter.value !== 'number' ? parameter.value : parseInt(parameter.value);
buffer.writeUInt8(8);
buffer.writeInt64LE(val);
} else {
const value = parameter.value;
if (value === null) {
buffer.writeUInt8(0);
} else {
buffer.writeUInt8(8);
buffer.writeInt64LE(value);
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === null || value === undefined) {
return null;
}
if (isNaN(value)) {
return new TypeError('Invalid number.');
}
if (value < -9007199254740991 || value > 9007199254740991) {
// Number.MIN_SAFE_INTEGER = -9007199254740991
// Number.MAX_SAFE_INTEGER = 9007199254740991
// 9007199254740991 = (2**53) - 1
// Can't use Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER directly though
// as these constants are not available in node 0.10.
return new TypeError('Value must be between -9007199254740991 and 9007199254740991, inclusive.' +
' For bigger numbers, use VarChar type.');

const numberValue = typeof value === 'number' ? value : parseInt(value);
if (!Number.isSafeInteger(numberValue)) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return numberValue;
}
};
18 changes: 11 additions & 7 deletions src/data-types/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,25 @@ module.exports = {
},

writeParameterData: function(buffer, parameter) {
if (parameter.value != null) {
buffer.writeUInt16LE(parameter.length);
buffer.writeBuffer(parameter.value.slice(0, Math.min(parameter.length, this.maximumLength)));
const value = parameter.value;

if (value != null) {
buffer.writeUInt16LE(value.length);
buffer.writeBuffer(value);
} else {
buffer.writeUInt16LE(NULL);
}
},

validate: function(value) {
if (value == null) {
validate(value, length) {
if (value === undefined || value === null) {
return null;
}
if (!Buffer.isBuffer(value)) {
return new TypeError('Invalid buffer.');

if (!Buffer.isBuffer(value) || value.length > length) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}

return value;
}
};
13 changes: 5 additions & 8 deletions src/data-types/bit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,19 @@ module.exports = {
},

writeParameterData: function(buffer, parameter) {
if (typeof parameter.value === 'undefined' || parameter.value === null) {
if (parameter.value === null) {
buffer.writeUInt8(0);
} else {
buffer.writeUInt8(1);
buffer.writeUInt8(parameter.value ? 1 : 0);
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === undefined || value === null) {
return null;
}
if (value) {
return true;
} else {
return false;
}

return !!value;
}
};
16 changes: 8 additions & 8 deletions src/data-types/char.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ module.exports = {
}
},

validate: function(value) {
if (value == null) {
validate(value, length) {
if (value === undefined || value === null) {
return null;
}
if (typeof value !== 'string') {
if (typeof value.toString !== 'function') {
return TypeError('Invalid string.');
}
value = value.toString();

const stringValue = typeof value !== 'string' && typeof value.toString === 'function' ? value.toString() : value;
if (typeof stringValue !== 'string' || (length && stringValue.length > length) || stringValue.length > this.maximumLength) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return stringValue;
}
};
20 changes: 13 additions & 7 deletions src/data-types/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,22 @@ module.exports = {
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === undefined || value === null) {
return null;
}
if (!(value instanceof Date)) {
value = Date.parse(value);

let dateValue;
if (value instanceof Date) {
dateValue = value;
} else {
dateValue = new Date(Date.parse(value));
}
if (isNaN(value)) {
return new TypeError('Invalid date.');

if (isNaN(dateValue)) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return dateValue;
}
};
20 changes: 13 additions & 7 deletions src/data-types/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,22 @@ module.exports = {
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === undefined || value === null) {
return null;
}
if (!(value instanceof Date)) {
value = Date.parse(value);

let dateValue;
if (value instanceof Date) {
dateValue = value;
} else {
dateValue = new Date(Date.parse(value));
}
if (isNaN(value)) {
return new TypeError('Invalid date.');

if (isNaN(dateValue)) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return dateValue;
}
};
37 changes: 21 additions & 16 deletions src/data-types/datetime2.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ module.exports = {
},

writeParameterData: function(buffer, parameter, options) {
if (parameter.value != null) {
const time = new Date(+parameter.value);

const value = parameter.value;
if (value != null) {
let timestamp;
if (options.useUTC) {
timestamp = ((time.getUTCHours() * 60 + time.getUTCMinutes()) * 60 + time.getUTCSeconds()) * 1000 + time.getUTCMilliseconds();
timestamp = ((value.getUTCHours() * 60 + value.getUTCMinutes()) * 60 + value.getUTCSeconds()) * 1000 + value.getUTCMilliseconds();
} else {
timestamp = ((time.getHours() * 60 + time.getMinutes()) * 60 + time.getSeconds()) * 1000 + time.getMilliseconds();
timestamp = ((value.getHours() * 60 + value.getMinutes()) * 60 + value.getSeconds()) * 1000 + value.getMilliseconds();
}

timestamp = timestamp * Math.pow(10, parameter.scale - 3);
timestamp += (parameter.value.nanosecondDelta != null ? parameter.value.nanosecondDelta : 0) * Math.pow(10, parameter.scale);
timestamp += (value.nanosecondDelta != null ? value.nanosecondDelta : 0) * Math.pow(10, parameter.scale);
timestamp = Math.round(timestamp);

switch (parameter.scale) {
Expand All @@ -77,26 +76,32 @@ module.exports = {
buffer.writeUInt40LE(timestamp);
}
if (options.useUTC) {
buffer.writeUInt24LE(Math.floor((+parameter.value - UTC_YEAR_ONE) / 86400000));
buffer.writeUInt24LE(Math.floor((+value - UTC_YEAR_ONE) / 86400000));
} else {
const dstDiff = -(parameter.value.getTimezoneOffset() - YEAR_ONE.getTimezoneOffset()) * 60 * 1000;
buffer.writeUInt24LE(Math.floor((+parameter.value - YEAR_ONE + dstDiff) / 86400000));
const dstDiff = -(value.getTimezoneOffset() - YEAR_ONE.getTimezoneOffset()) * 60 * 1000;
buffer.writeUInt24LE(Math.floor((+value - YEAR_ONE + dstDiff) / 86400000));
}
} else {
buffer.writeUInt8(0);
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === undefined || value === null) {
return null;
}
if (!(value instanceof Date)) {
value = Date.parse(value);

let dateValue;
if (value instanceof Date) {
dateValue = value;
} else {
dateValue = new Date(Date.parse(value));
}
if (isNaN(value)) {
return new TypeError('Invalid date.');

if (isNaN(dateValue)) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return dateValue;
}
};
32 changes: 20 additions & 12 deletions src/data-types/datetimeoffset.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,18 @@ module.exports = {
buffer.writeUInt8(parameter.scale);
},
writeParameterData: function(buffer, parameter) {
if (parameter.value != null) {
const time = new Date(+parameter.value);
const value = parameter.value;
if (value != null) {
const time = new Date(+value);
time.setUTCFullYear(1970);
time.setUTCMonth(0);
time.setUTCDate(1);

let timestamp = time * Math.pow(10, parameter.scale - 3);
timestamp += (parameter.value.nanosecondDelta != null ? parameter.value.nanosecondDelta : 0) * Math.pow(10, parameter.scale);
timestamp += (value.nanosecondDelta != null ? value.nanosecondDelta : 0) * Math.pow(10, parameter.scale);
timestamp = Math.round(timestamp);

const offset = -parameter.value.getTimezoneOffset();
const offset = -value.getTimezoneOffset();
switch (parameter.scale) {
case 0:
case 1:
Expand All @@ -67,22 +68,29 @@ module.exports = {
buffer.writeUInt8(10);
buffer.writeUInt40LE(timestamp);
}
buffer.writeUInt24LE(Math.floor((+parameter.value - UTC_YEAR_ONE) / 86400000));
buffer.writeUInt24LE(Math.floor((+value - UTC_YEAR_ONE) / 86400000));
buffer.writeInt16LE(offset);
} else {
buffer.writeUInt8(0);
}
},
validate: function(value) {
if (value == null) {

validate(value) {
if (value === undefined || value === null) {
return null;
}
if (!(value instanceof Date)) {
value = Date.parse(value);

let dateValue;
if (value instanceof Date) {
dateValue = value;
} else {
dateValue = new Date(Date.parse(value));
}
if (isNaN(value)) {
return new TypeError('Invalid date.');

if (isNaN(dateValue)) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}
return value;

return dateValue;
}
};
20 changes: 14 additions & 6 deletions src/data-types/decimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,22 @@ module.exports = {
}
},

validate: function(value) {
if (value == null) {
validate(value) {
if (value === undefined || value === null) {
return null;
}
value = parseFloat(value);
if (isNaN(value)) {
return new TypeError('Invalid number.');

let numberValue;
if (typeof value === 'number') {
numberValue = value;
} else {
numberValue = parseFloat(value);
}
return value;

if (!Number.isFinite(numberValue) || (typeof value === 'string' && value !== numberValue.toString())) {
return new TypeError(`The given value could not be converted to ${this.name}`);
}

return numberValue;
}
};

0 comments on commit 9ae5f2f

Please sign in to comment.