Skip to content

Commit

Permalink
GEOMETRY type support. Fixes sidorares#93
Browse files Browse the repository at this point in the history
  • Loading branch information
sidorares committed Apr 26, 2014
1 parent 7af7656 commit ebd30fd
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 4 deletions.
20 changes: 16 additions & 4 deletions lib/compile_text_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ var Charsets = require('./constants/charsets');
var vm = require('vm');
var srcEscape = require('./helpers').srcEscape;

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

function compile(fields) {

var result = [];
var i=0;

result.push('(function() { return function TextRow(packet) {');
for (i = 0; i < fields.length; i++) {
//debug: uncomment to see each column metadata as comment
//result.push(' // ' + typeNames[fields[i].columnType] + ': ' + JSON.stringify(fields[i]));
result.push(' this['+ srcEscape(fields[i].name) + '] = ' + readCodeFor(fields[i].columnType, fields[i].characterSet));
}
result.push('};})()');
var src = result.join('\n');
//console.log(src);
return vm.runInThisContext(src);
}

Expand All @@ -23,23 +32,26 @@ function readCodeFor(type, charset) {
case Types.LONG:
case Types.INT24:
case Types.YEAR:
case Types.LONG:
case Types.LONGLONG:
return "packet.parseLengthCodedInt();";
case Types.FLOAT:
case Types.DOUBLE:
return "packet.parseLengthCodedFloat();";
case Types.NULL:
return "null; packet.skip(1);";
case Types.LONG:
case Types.LONGLONG:
case Types.DECIMAL:
case Types.NEWDECIMAL:
return "packet.readLengthCodedString(); //" + type + ' ' + charset;
case Types.DATE:
return "packet.parseDate();";
case Types.DATETIME:
case Types.TIMESTAMP:
// TODO: add parseDateTime and parseTimestamp (it's much faster then converting to string from buffer and using Date as parser)
return "new Date(packet.readLengthCodedString());";
return "packet.parseDateTime();";
case Types.TIME:
return "packet.readLengthCodedString()";
case Types.GEOMETRY:
return "packet.parseGeometryValue();";
default:
if (charset == Charsets.BINARY)
return "packet.readLengthCodedBuffer();";
Expand Down
68 changes: 68 additions & 0 deletions lib/packets/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,68 @@ Packet.prototype.parseInt = function(len) {
return result*sign;
};

// copy-paste from https://github.com/felixge/node-mysql/blob/master/lib/protocol/Parser.js
Packet.prototype.parseGeometryValue = function() {
var buffer = this.readLengthCodedBuffer();
var offset = 4;

if (buffer === null || !buffer.length) {
return null;
}

function parseGeometry() {
var result = null;
var byteOrder = buffer.readUInt8(offset); offset += 1;
var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
switch(wkbType) {
case 1: // WKBPoint
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result = {x: x, y: y};
break;
case 2: // WKBLineString
var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for(var i=numPoints;i>0;i--) {
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
result.push({x: x, y: y});
}
break;
case 3: // WKBPolygon
var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
result = [];
for(var i=numRings;i>0;i--) {
var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
var line = [];
for(var j=numPoints;j>0;j--) {
var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
line.push({x: x, y: y});
}
result.push(line);
}
break;
case 4: // WKBMultiPoint
case 5: // WKBMultiLineString
case 6: // WKBMultiPolygon
case 7: // WKBGeometryCollection
var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
var result = [];
for(var i=num;i>0;i--) {
result.push(parseGeometry());
}
break;
}
return result;
}
return parseGeometry();
};

Packet.prototype.parseDate = function() {
var strLen = this.readLengthCodedNumber();
if (strLen === null)
return null;
if (strLen != 10) {
// we expect only YYYY-MM-DD here.
// if for some reason it's not the case return invalid date
Expand All @@ -269,6 +329,14 @@ Packet.prototype.parseDate = function() {
return new Date(y, m-1, d);
}

Packet.prototype.parseDateTime = function() {
var str = this.readLengthCodedString();
if (str === null)
return null;
return new Date(str);
}


// TODO: handle E notation
var dot = '.'.charCodeAt(0);
var exponent = 'e'.charCodeAt(0);
Expand Down
34 changes: 34 additions & 0 deletions test/integration/connection/test-type-cast-null-fields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var common = require('../../common');
var connection = common.createConnection();
var assert = require('assert');

common.useTestDb(connection);

var table = 'insert_test';
connection.query([
'CREATE TEMPORARY TABLE `' + table + '` (',
'`id` int(11) unsigned NOT NULL AUTO_INCREMENT,',
'`date` DATETIME NULL,',
'`number` INT NULL,',
'PRIMARY KEY (`id`)',
') ENGINE=InnoDB DEFAULT CHARSET=utf8'
].join('\n'));

connection.query('INSERT INTO ' + table + ' SET ?', {
date : null,
number : null,
});

var results;
connection.query('SELECT * FROM ' + table, function(err, _results) {
if (err) throw err;

results = _results;
});

connection.end();

process.on('exit', function() {
assert.strictEqual(results[0].date, null);
assert.strictEqual(results[0].number, null);
});
119 changes: 119 additions & 0 deletions test/integration/connection/test-type-casting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
var common = require('../../common');
var connection = common.createConnection();
var assert = require('assert');

common.useTestDb(connection);

var tests = [
{type: 'decimal(4,3)', insert: '1.234', expect: '1.234' },
// {type: 'decimal(3,3)', insert: 0.33},
{type: 'tinyint', insert: 1},
{type: 'smallint', insert: 2},
{type: 'int', insert: 3},
{type: 'float', insert: 4.5},
{type: 'double', insert: 5.5},
{type: 'bigint', insert: '6', expect: 6},
{type: 'bigint', insert: 6},
{type: 'mediumint', insert: 7},
{type: 'year', insert: 2012},
{type: 'timestamp', insert: new Date('2012-05-12 11:00:23')},
{type: 'datetime', insert: new Date('2012-05-12 12:00:23')},
{type: 'date', insert: new Date('2012-05-12 00:00:00')},
{type: 'time', insert: '13:13:23'},
{type: 'binary(4)', insert: new Buffer([0, 1, 254, 255])},
{type: 'varbinary(4)', insert: new Buffer([0, 1, 254, 255])},
{type: 'tinyblob', insert: new Buffer([0, 1, 254, 255])},
{type: 'mediumblob', insert: new Buffer([0, 1, 254, 255])},
{type: 'longblob', insert: new Buffer([0, 1, 254, 255])},
{type: 'blob', insert: new Buffer([0, 1, 254, 255])},
{type: 'bit(32)', insert: new Buffer([0, 1, 254, 255])},
{type: 'char(5)', insert: 'Hello'},
{type: 'varchar(5)', insert: 'Hello'},
{type: 'varchar(3) character set utf8 collate utf8_bin', insert: 'bin'},
{type: 'tinytext', insert: 'Hello World'},
{type: 'mediumtext', insert: 'Hello World'},
{type: 'longtext', insert: 'Hello World'},
{type: 'text', insert: 'Hello World'},
{type: 'point', insertRaw: 'POINT(1.2,-3.4)', expect: {x:1.2, y:-3.4}, deep: true},
{type: 'point', insertRaw: (function() {
var buffer = new Buffer(21);
buffer.writeUInt8(1, 0);
buffer.writeUInt32LE(1, 1);
buffer.writeDoubleLE(-5.6, 5);
buffer.writeDoubleLE(10.23, 13);
return 'GeomFromWKB(' + connection.escape(buffer) + ')';
})(), expect: {x:-5.6, y:10.23}, deep: true},
{type: 'point', insertRaw: '', insert: null, expect: null},
{type: 'linestring', insertRaw: 'LINESTRING(POINT(1.2,-3.4),POINT(-5.6,10.23),POINT(0.2,0.7))', expect: [{x:1.2, y:-3.4}, {x:-5.6, y:10.23}, {x:0.2, y:0.7}], deep: true},
{type: 'polygon', insertRaw: "GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))')", expect: [[{x:0,y:0},{x:10,y:0},{x:10,y:10},{x:0,y:10},{x:0,y:0}],[{x:5,y:5},{x:7,y:5},{x:7,y:7},{x:5,y:7},{x:5,y:5}]], deep: true},
{type: 'geometry', insertRaw: 'POINT(1.2,-3.4)', expect: {x:1.2, y:-3.4}, deep: true},
{type: 'multipoint', insertRaw: "GeomFromText('MULTIPOINT(0 0, 20 20, 60 60)')", expect: [{x:0, y:0}, {x:20, y:20}, {x:60, y:60}], deep: true},
{type: 'multilinestring', insertRaw: "GeomFromText('MULTILINESTRING((10 10, 20 20), (15 15, 30 15))')", expect: [[{x:10,y:10},{x:20,y:20}],[{x:15,y:15},{x:30,y:15}]], deep: true},
{type: 'multipolygon', insertRaw: "GeomFromText('MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((5 5,7 5,7 7,5 7, 5 5)))')", expect: [[[{x:0,y:0},{x:10,y:0},{x:10,y:10},{x:0,y:10},{x:0,y:0}]],[[{x:5,y:5},{x:7,y:5},{x:7,y:7},{x:5,y:7},{x:5,y:5}]]], deep: true},
{type: 'geometrycollection', insertRaw: "GeomFromText('GEOMETRYCOLLECTION(POINT(11 10), POINT(31 30), LINESTRING(15 15, 20 20))')", expect: [{x:11,y:10},{x:31,y:30},[{x:15,y:15},{x:20,y:20}]], deep: true}
];

var table = 'type_casting';

var schema = [];
var inserts = [];

tests.forEach(function(test, index) {
var escaped = test.insertRaw || connection.escape(test.insert);

test.columnName = test.type + '_' + index;

schema.push('`' + test.columnName + '` ' + test.type + ',');
inserts.push('`' + test.columnName + '` = ' + escaped);
});

var createTable = [
'CREATE TEMPORARY TABLE `' + table + '` (',
'`id` int(11) unsigned NOT NULL AUTO_INCREMENT,'
].concat(schema).concat([
'PRIMARY KEY (`id`)',
') ENGINE=InnoDB DEFAULT CHARSET=utf8'
]).join('\n');

connection.query(createTable);

connection.query('INSERT INTO ' + table + ' SET' + inserts.join(',\n'));

var row;
connection.query('SELECT * FROM type_casting', function(err, rows) {
if (err) throw err;

row = rows[0];
});

connection.end();

process.on('exit', function() {
tests.forEach(function(test) {
var expected = test.expect || test.insert;
var got = row[test.columnName];
var message;

if (expected instanceof Date) {
assert.equal(got instanceof Date, true, test.type);

expected = String(expected);
got = String(got);
} else if (Buffer.isBuffer(expected)) {
assert.equal(Buffer.isBuffer(got), true, test.type);

expected = String(Array.prototype.slice.call(expected));
got = String(Array.prototype.slice.call(got));
}

if (test.deep) {
message = 'got: "' + JSON.stringify(got) + '" expected: "' + JSON.stringify(expected) +
'" test: ' + test.type + '';
assert.deepEqual(expected, got, message);
} else {
message = 'got: "' + got + '" (' + (typeof got) + ') expected: "' + expected +
'" (' + (typeof expected) + ') test: ' + test.type + '';
assert.strictEqual(expected, got, message);
}
});
});

0 comments on commit ebd30fd

Please sign in to comment.