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

Supercharge prepareValue #555

Merged
merged 7 commits into from Apr 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 46 additions & 0 deletions benchmark/prepare-values.js
@@ -0,0 +1,46 @@
var utils = require("../lib/utils");

var numArr = [];
for (var i = 0; i < 1000; i++) numArr[i] = i;
console.time("prepare-number-array");
for (var i = 0; i < 100; i++) {
utils.prepareValue(numArr);
}
console.timeEnd("prepare-number-array");


var strArr = new Array(10000);
console.time("prepare-string-array");
for (var i = 0; i < 100; i++) {
utils.prepareValue(strArr);
}
console.timeEnd("prepare-string-array");


var objArr = [];
for (var i = 0; i < 1000; i++) objArr[i] = { x: { y: 42 }};
console.time("prepare-object-array");
for (var i = 0; i < 100; i++) {
utils.prepareValue(objArr);
}
console.timeEnd("prepare-object-array");


var obj = { x: { y: 42 }};
console.time("prepare-object");
for (var i = 0; i < 100000; i++) {
utils.prepareValue(obj);
}
console.timeEnd("prepare-object");


var customType = {
toPostgres: function () {
return { toPostgres: function () { return new Date(); } };
}
};
console.time("prepare-custom-type");
for (var i = 0; i < 100000; i++) {
utils.prepareValue(customType);
}
console.timeEnd("prepare-custom-type");
67 changes: 38 additions & 29 deletions lib/utils.js
Expand Up @@ -17,53 +17,62 @@ if(typeof events.EventEmitter.prototype.once !== 'function') {
// uses comma separator so won't work for types like box that use
// a different array separator.
function arrayString(val) {
var result = '{';
for (var i = 0 ; i < val.length; i++) {
if(i > 0) {
result = result + ',';
}
if(val[i] instanceof Date) {
result = result + JSON.stringify(val[i]);
}
else if(typeof val[i] === 'undefined') {
result = result + 'NULL';
}
else if(Array.isArray(val[i])) {
result = result + arrayString(val[i]);
}
else
{
result = result +
(val[i] === null ? 'NULL' : JSON.stringify(val[i]));
}
}
result = result + '}';
return result;
var result = '{';
for (var i = 0 ; i < val.length; i++) {
if(i > 0) {
result = result + ',';
}
if(val[i] === null || typeof val[i] === 'undefined') {
result = result + 'NULL';
}
else if(Array.isArray(val[i])) {
result = result + arrayString(val[i]);
}
else
{
result = result + JSON.stringify(prepareValue(val[i]));
}
}
result = result + '}';
return result;
}

//converts values from javascript types
//to their 'raw' counterparts for use as a postgres parameter
//note: you can override this function to provide your own conversion mechanism
//for complex types, etc...
var prepareValue = function(val) {
var prepareValue = function(val, seen) {
if (val instanceof Buffer) {
return val;
}
if(val instanceof Date) {
return dateToString(val);
}
if(typeof val === 'undefined') {
return null;
}
if(Array.isArray(val)) {
return arrayString(val);
}
if(!val || typeof val !== 'object') {
return val === null ? null : val.toString();
if(val === null || typeof val === 'undefined') {
return null;
}
if(typeof val === 'object') {
return prepareObject(val, seen);
}
return JSON.stringify(val);
return val.toString();
};

function prepareObject(val, seen) {
if(val.toPostgres && typeof val.toPostgres === 'function') {
seen = seen || [];
if (seen.indexOf(val) !== -1) {
throw new Error('circular reference detected while preparing "' + val + '" for query');
}
seen.push(val);

return prepareValue(val.toPostgres(prepareValue), seen);
}
return JSON.stringify(val);
}

function dateToString(date) {
function pad(number, digits) {
number = ""+number;
Expand Down
13 changes: 12 additions & 1 deletion test/test-helper.js
Expand Up @@ -222,14 +222,25 @@ var Sink = function(expected, timeout, callback) {
}
}

var getTimezoneOffset = Date.prototype.getTimezoneOffset;

var setTimezoneOffset = function(minutesOffset) {
Date.prototype.getTimezoneOffset = function () { return minutesOffset; };
}

var resetTimezoneOffset = function() {
Date.prototype.getTimezoneOffset = getTimezoneOffset;
}

module.exports = {
Sink: Sink,
pg: require(__dirname + '/../lib/'),
args: args,
config: args,
sys: sys,
Client: Client
Client: Client,
setTimezoneOffset: setTimezoneOffset,
resetTimezoneOffset: resetTimezoneOffset
};


125 changes: 124 additions & 1 deletion test/unit/utils-tests.js
@@ -1,4 +1,4 @@
require(__dirname + '/test-helper');
var helper = require(__dirname + '/test-helper');
var utils = require(__dirname + "/../../lib/utils");
var defaults = require(__dirname + "/../../lib").defaults;

Expand Down Expand Up @@ -48,3 +48,126 @@ test('normalizing query configs', function() {
config = utils.normalizeQueryConfig({text: 'TEXT', values: [10]}, callback)
assert.deepEqual(config, {text: 'TEXT', values: [10], callback: callback})
})

test('prepareValues: buffer prepared properly', function() {
var buf = new Buffer("quack");
var out = utils.prepareValue(buf);
assert.strictEqual(buf, out);
});

test('prepareValues: date prepared properly', function() {
helper.setTimezoneOffset(-330);

var date = new Date(2014, 1, 1, 11, 11, 1, 7);
var out = utils.prepareValue(date);
assert.strictEqual(out, "2014-02-01T11:11:01.007+05:30");

helper.resetTimezoneOffset();
});

test('prepareValues: undefined prepared properly', function() {
var out = utils.prepareValue(void 0);
assert.strictEqual(out, null);
});

test('prepareValue: null prepared properly', function() {
var out = utils.prepareValue(null);
assert.strictEqual(out, null);
});

test('prepareValue: true prepared properly', function() {
var out = utils.prepareValue(true);
assert.strictEqual(out, 'true');
});

test('prepareValue: false prepared properly', function() {
var out = utils.prepareValue(false);
assert.strictEqual(out, 'false');
});

test('prepareValue: number prepared properly', function () {
var out = utils.prepareValue(3.042);
assert.strictEqual(out, '3.042');
});

test('prepareValue: string prepared properly', function() {
var out = utils.prepareValue('big bad wolf');
assert.strictEqual(out, 'big bad wolf');
});

test('prepareValue: simple array prepared properly', function() {
var out = utils.prepareValue([1, null, 3, undefined, [5, 6, "squ,awk"]]);
assert.strictEqual(out, '{"1",NULL,"3",NULL,{"5","6","squ,awk"}}');
});

test('prepareValue: complex array prepared properly', function() {
var out = utils.prepareValue([{ x: 42 }, { y: 84 }]);
assert.strictEqual(out, '{"{\\"x\\":42}","{\\"y\\":84}"}');
});

test('prepareValue: date array prepared properly', function() {
helper.setTimezoneOffset(-330);

var date = new Date(2014, 1, 1, 11, 11, 1, 7);
var out = utils.prepareValue([date]);
assert.strictEqual(out, '{"2014-02-01T11:11:01.007+05:30"}');

helper.resetTimezoneOffset();
});

test('prepareValue: arbitrary objects prepared properly', function() {
var out = utils.prepareValue({ x: 42 });
assert.strictEqual(out, '{"x":42}');
});

test('prepareValue: objects with simple toPostgres prepared properly', function() {
var customType = {
toPostgres: function() {
return "zomgcustom!";
}
};
var out = utils.prepareValue(customType);
assert.strictEqual(out, "zomgcustom!");
});

test('prepareValue: objects with complex toPostgres prepared properly', function() {
var buf = new Buffer("zomgcustom!");
var customType = {
toPostgres: function() {
return [1, 2];
}
};
var out = utils.prepareValue(customType);
assert.strictEqual(out, '{"1","2"}');
});

test('prepareValue: objects with toPostgres receive prepareValue', function() {
var customRange = {
lower: { toPostgres: function() { return 5; } },
upper: { toPostgres: function() { return 10; } },
toPostgres: function(prepare) {
return "[" + prepare(this.lower) + "," + prepare(this.upper) + "]";
}
};
var out = utils.prepareValue(customRange);
assert.strictEqual(out, "[5,10]");
});

test('prepareValue: objects with circular toPostgres rejected', function() {
var buf = new Buffer("zomgcustom!");
var customType = {
toPostgres: function() {
return { toPostgres: function () { return customType; } };
}
};

//can't use `assert.throws` since we need to distinguish circular reference
//errors from call stack exceeded errors
try {
utils.prepareValue(customType);
} catch (e) {
assert.ok(e.message.match(/circular/), "Expected circular reference error but got " + e);
return;
}
throw new Error("Expected prepareValue to throw exception");
});