var EventEmitter = require('events').EventEmitter; var util = require('util'); var Result = require(__dirname + "/result"); var Types = require(__dirname + "/types"); var Query = function(config) { this.text = config.text; this.values = config.values; this.rows = config.rows; this.types = config.types; this.name = config.name; this.binary = config.binary; //use unique portal name each time this.portal = config.portal || "" this.callback = config.callback; this._fieldNames = []; this._fieldConverters = []; this._result = new Result(); this.isPreparedStatement = false; EventEmitter.call(this); }; util.inherits(Query, EventEmitter); var p = Query.prototype; p.requiresPreparation = function() { return (this.values || 0).length > 0 || this.name || this.rows || this.binary; }; var noParse = function(val) { return val; }; //associates row metadata from the supplied //message with this query object //metadata used when parsing row results p.handleRowDescription = function(msg) { this._fieldNames = []; this._fieldConverters = []; var len = msg.fields.length; for(var i = 0; i < len; i++) { var field = msg.fields[i]; var format = field.format; this._fieldNames[i] = field.name; this._fieldConverters[i] = Types.getTypeParser(field.dataTypeID, format); }; }; p.handleDataRow = function(msg) { var self = this; var row = {}; for(var i = 0; i < msg.fields.length; i++) { var rawValue = msg.fields[i]; if(rawValue === null) { //leave null values alone row[self._fieldNames[i]] = null; } else { //convert value to javascript row[self._fieldNames[i]] = self._fieldConverters[i](rawValue); } } self.emit('row', row, self._result); //if there is a callback collect rows if(self.callback) { self._result.addRow(row); } }; p.handleCommandComplete = function(msg) { this._result.addCommandComplete(msg); }; p.handleReadyForQuery = function() { if(this.callback) { this.callback(null, this._result); } this.emit('end', this._result); }; p.handleError = function(err) { //if callback supplied do not emit error event as uncaught error //events will bubble up to node process if(this.callback) { this.callback(err) } else { this.emit('error', err); } this.emit('end'); }; p.submit = function(connection) { var self = this; if(this.requiresPreparation()) { this.prepare(connection); } else { connection.query(this.text); } }; p.hasBeenParsed = function(connection) { return this.name && connection.parsedStatements[this.name]; }; p.getRows = function(connection) { connection.execute({ portal: this.portalName, rows: this.rows }, true); connection.flush(); }; p.prepare = function(connection) { var self = this; //prepared statements need sync to be called after each command //complete or when an error is encountered this.isPreparedStatement = true; //TODO refactor this poor encapsulation if(!this.hasBeenParsed(connection)) { connection.parse({ text: self.text, name: self.name, types: self.types }, true); connection.parsedStatements[this.name] = true; } //TODO is there some better way to prepare values for the database? if(self.values) { self.values = self.values.map(function(val) { return (val instanceof Date) ? JSON.stringify(val) : val; }); } //http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY connection.bind({ portal: self.portalName, statement: self.name, values: self.values, binary: self.binary }, true); connection.describe({ type: 'P', name: self.portalName || "" }, true); this.getRows(connection); }; module.exports = Query;