Skip to content

Commit

Permalink
nestTables and rowsAsArray in prepared statements
Browse files Browse the repository at this point in the history
  • Loading branch information
sidorares committed Apr 27, 2014
1 parent 0638237 commit dda4d7a
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 32 deletions.
41 changes: 26 additions & 15 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ var Packets = require('../packets/index.js');
var util = require('util');
var compileParser = require('../compile_binary_parser');

function Execute(sql, parameters, callback)
function Execute(options, callback)
{
Command.call(this);
this.query = sql;
this.query = options.sql;

this.onResult = callback; // TODO check felixge multi-result api
this.fieldCount = 0;
this.parameterCount = 0;
Expand All @@ -17,14 +18,15 @@ function Execute(sql, parameters, callback)
this.resultFieldCount = 0;
this.insertId = 0;

this.parameters = parameters;
this.parameters = options.values;
this.rows = [];
this.statementInfo = null;
this.options = options;
}
util.inherits(Execute, Command);

Execute.prototype.start = function(packet, connection) {
var cachedStatement = connection.statements[this.query];
var cachedStatement = connection.statements[statementKey(this.query, this.options)];
if (!cachedStatement) { // prepare first
connection.writePacket(new Packets.PrepareStatement(this.query).toPacket(1));
} else {
Expand All @@ -44,7 +46,8 @@ Execute.prototype.prepareHeader = function(packet, connection) {
this.fieldCount = header.fieldCount;
this.parameterCount = header.parameterCount;
this.statementInfo = new PreparedStatementInfo(header.id);
connection.statements[this.query] = this.statementInfo;

connection.statements[statementKey(this.query, this.options)] = this.statementInfo;
if (this.parameterCount > 0)
return Execute.prototype.readParameter;
else if (this.fieldCount > 0)
Expand All @@ -61,9 +64,15 @@ Execute.prototype.readParameter = function(packet, connection) {
return this.readParameter;
};

function statementKey(query, options) {
return (typeof options.nestTables) +
'/' + options.nestTables + '/' + options.rowsAsHash
+ query;
}

// TODO: move to connection.js?
function getFieldsKey(fields) {
var res = '';
function getFieldsKey(fields, options) {
var res = (typeof options.nestTables) + '/' + options.nestTables + '/' + options.rowsAsHash;
for (var i=0; i < fields.length; ++i)
res += '/' + fields[i].name + ':' + fields[i].columnType + ':' + fields[i].flags;
return res;
Expand All @@ -72,6 +81,7 @@ function getFieldsKey(fields) {
Execute.prototype.readField = function(packet, connection) {
var def = new Packets.ColumnDefinition(packet);
this.fields.push(def);
var parserKey = '';

// TODO: api to allow to flag "I'm not going to change schema for this statement"
// this way we can ignore column definitions in binary response and use
Expand All @@ -80,14 +90,16 @@ Execute.prototype.readField = function(packet, connection) {
// (without reconnecting) you are in trouble

if (this.fields.length == this.fieldCount) {
// compile row parser
var parserKey = getFieldsKey(this.fields);
// try cached first
this.statementInfo.fields = this.fields;
this.statementInfo.parser = connection.binaryProtocolParsers[parserKey];
if (!this.statementInfo.parser) {
this.statementInfo.parser = compileParser(this.fields);
connection.binaryProtocolParsers[parserKey] = this.statementInfo.parser;
// compile row parser
parserKey = getFieldsKey(this.fields, this.options);
// try cached first
this.statementInfo.fields = this.fields;
this.statementInfo.parser = connection.binaryProtocolParsers[parserKey];
if (!this.statementInfo.parser) {
this.statementInfo.parser = compileParser(this.fields, this.options, connection.config);
connection.binaryProtocolParsers[parserKey] = this.statementInfo.parser;
}
}
return Execute.prototype.fieldsEOF;
}
Expand Down Expand Up @@ -166,7 +178,6 @@ Execute.prototype.row = function(packet)
})
return null;
}

var r = new this.statementInfo.parser(packet);
if (this.onResult)
this.rows.push(r);
Expand Down
3 changes: 1 addition & 2 deletions lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ Query.prototype.resultsetHeader = function(packet) {

// TODO: move to connection.js ?
function getFieldsKey(fields, options) {
var res = '';
res += (typeof options.nestTables) + '/' + options.nestTables + '/' + options.rowsAsHash
var res = (typeof options.nestTables) + '/' + options.nestTables + '/' + options.rowsAsHash;
for (var i=0; i < fields.length; ++i)
res += '/' + fields[i].name + ':' + fields[i].columnType + ':' + fields[i].flags;
return res;
Expand Down
63 changes: 53 additions & 10 deletions lib/compile_binary_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,67 @@ var Types = require('./constants/types');
var vm = require('vm');
var srcEscape = require('./helpers').srcEscape;

function compile(fields) {
var typeNames = [];
for (var t in Types) {
typeNames[Types[t]] = t;
}

function compile(fields, options, config) {
var result = [];
var i=0;
var j=0;
var nullBitmapLength = Math.floor((fields.length + 7 + 2) / 8);
result.push('(function(){ return function BinaryRow(packet) {');

if (options.rowsAsArray)
result.push(' var result = new Array(' + fields.length + ')');

var resultTables = {};
var resultTablesArray = [];

if (typeof options.nestTables == 'boolean') {
for (i = 0; i < fields.length; i++) {
resultTables[fields[i].table] = 1;
}
resultTablesArray = Object.keys(resultTables);
for (i = 0; i < resultTablesArray.length; i++) {
result.push(' this[' + srcEscape(resultTablesArray[i]) + '] = {};');
}
}

result.push(' var statusByte = packet.readInt8();');
for (j=0; j < nullBitmapLength; ++j)
result.push(' var nullBitmaskByte' + j + ' = packet.readInt8();');
for (i=0; i < nullBitmapLength; ++i)
result.push(' var nullBitmaskByte' + i + ' = packet.readInt8();');

var lvalue = '';
var currentFieldNullBit = 4;
var nullByteIndex = 0;
var fieldName = '';
var tableName = '';

for (i = 0; i < fields.length; i++) {
//result.push(' // type = ' + fields[i].columnType + ' flags = ' + fields[i].flags);
var name = srcEscape(fields[i].name);
fieldName = srcEscape(fields[i].name);
result.push(' // ' + fieldName + ': '+ typeNames[fields[i].columnType]);

if (typeof options.nestTables == 'string') {
tableName = srcEscape(fields[i].table);
lvalue = [' this[', srcEscape(fields[i].table + options.nestTables + fields[i].name), ']'].join('');
} else if (typeof options.nestTables == 'boolean') {
tableName = srcEscape(fields[i].table);
lvalue = [' this[', tableName, '][', fieldName, ']'].join('');
} else if (options.rowsAsArray) {
lvalue = ' result[' + i.toString(10) + ']';
} else
lvalue = ' this[' + srcEscape(fields[i].name) + ']';

if (fields[i].flags & FieldFlags.NOT_NULL) { // don't need to check null bitmap if field can't be null.
result.push(' this[' + name + '] = ' + readCodeFor(fields[i]));
result.push(lvalue + ' = ' + readCodeFor(fields[i]));
} else if (fields[i].columnType == Types.NULL) {
result.push(' this[' + name + '] = null;');
result.push(lvalue + ' = null;');
} else {
result.push(' if (nullBitmaskByte' + nullByteIndex + ' & ' + currentFieldNullBit + ')');
result.push(' this[' + name + '] = null;');
result.push(' ' + lvalue + ' = null;');
result.push(' else');
result.push(' this[' + name + '] = ' + readCodeFor(fields[i]));
result.push(' ' + lvalue + ' = ' + readCodeFor(fields[i]));
}
currentFieldNullBit *= 2;
if (currentFieldNullBit == 0x100)
Expand All @@ -36,8 +73,14 @@ function compile(fields) {
nullByteIndex++;
}
}

if (options.rowsAsArray)
result.push(' return result;');

result.push('}; })()');
var src = result.join('\n');
//var cardinal = require('cardinal');
//console.log(cardinal.highlight(src));
return vm.runInThisContext(src);
}

Expand Down
11 changes: 8 additions & 3 deletions lib/compile_text_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function compile(fields, options, config) {

result.push('(function() { return function TextRow(packet) {');
if (options.rowsAsArray)
result.push('var result = new Array(' + fields.length + ')')
result.push(' var result = new Array(' + fields.length + ')');

var resultTables = {};
var resultTablesArray = [];
Expand Down Expand Up @@ -44,14 +44,19 @@ function compile(fields, options, config) {
tableName = srcEscape(fields[i].table);
lvalue = [' this[', tableName, '][', fieldName, ']'].join('');
} else if (options.rowsAsArray) {
lvalue = 'result[' + i.toString(10) + ']';
lvalue = ' result[' + i.toString(10) + ']';
} else
lvalue = ' this[' + srcEscape(fields[i].name) + ']';
result.push(lvalue + ' = ' + readCodeFor(fields[i].columnType, fields[i].characterSet));
}

if (options.rowsAsArray)
result.push(' return result;');

result.push('};})()');
var src = result.join('\n');
console.log(src);
//var cardinal = require('cardinal');
//console.log(cardinal.highlight(src));
return vm.runInThisContext(src);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ Connection.prototype.execute = function(sql, values, cb) {
options.values = values;
}

return this.addCommand(new Commands.Execute(options.sql, options.values, _domainify(cb)));
return this.addCommand(new Commands.Execute(options, _domainify(cb)));
};

// transaction helpers
Expand Down
40 changes: 39 additions & 1 deletion test/integration/connection/test-nested-tables-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,64 @@ var options2 = {
nestTables: '_',
sql: 'SELECT * FROM ' + table
};
var rows1, rows2;
var options3 = {
rowsAsArray: true,
sql: 'SELECT * FROM ' + table
};
var rows1, rows2, rows3, rows1e, rows2e, rows3e;

connection.query(options1, function(err, _rows) {
if (err) throw err;

rows1 = _rows;
});

connection.query(options2, function(err, _rows) {
if (err) throw err;

rows2 = _rows;
});

connection.query(options3, function(err, _rows) {
if (err) throw err;

rows3 = _rows;
});

connection.execute(options1, function(err, _rows) {
if (err) throw err;

rows1e = _rows;
});

connection.execute(options2, function(err, _rows) {
if (err) throw err;

rows2e = _rows;
});

connection.execute(options3, function(err, _rows) {
if (err) throw err;

rows3e = _rows;
});

connection.end();

process.on('exit', function() {
assert.equal(rows1.length, 1);
assert.equal(rows1[0].nested_test.id, 1);
assert.equal(rows1[0].nested_test.title, 'test');

assert.equal(rows2.length, 1);
assert.equal(rows2[0].nested_test_id, 1);
assert.equal(rows2[0].nested_test_title, 'test');

assert.equal(Array.isArray(rows3[0]), true);
assert.equal(rows3[0][0], 1);
assert.equal(rows3[0][1], 'test');

assert.deepEqual(rows1, rows1e);
assert.deepEqual(rows2, rows2e);
assert.deepEqual(rows3, rows3e);
});

0 comments on commit dda4d7a

Please sign in to comment.