From f4bc6f7ceb6498fed75717cdba32d05e6dedd47b Mon Sep 17 00:00:00 2001 From: Can Abacigil Date: Sat, 13 Aug 2016 20:27:58 +0300 Subject: [PATCH 1/2] # Adjustment of result object for multiple statements - If you query multiple statements, current lib does not support and it returns wrong results in result object. - Now it supports multiple statements and it returns "result" object as an array and this array has Result object for each statement that requested in query. - Example for multiple statement : SELECT * FROM users;select * from events;update users set id = 1 where id = 1 - If you set "multipleStatementResult" to "true" in connection configuration object, this feature will be enabled, otherwise nothing will change, same functionality of original one will be executed. - Tested with SELECT, UPDATE, DELETE and INSERT # Simple example - An example for multiple statements is available in "sample" folder. --- lib/client.js | 2 +- lib/connection-parameters.js | 1 + lib/query.js | 119 ++++++++++++++++++++++------------- lib/result.js | 5 +- sample/example.js | 33 ++++++++++ 5 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 sample/example.js diff --git a/lib/client.js b/lib/client.js index 54ab017cb..e7ca754dd 100644 --- a/lib/client.js +++ b/lib/client.js @@ -323,7 +323,7 @@ Client.prototype.copyTo = function (text) { Client.prototype.query = function(config, values, callback) { //can take in strings, config object or query object var query = (typeof config.submit == 'function') ? config : - new Query(config, values, callback); + new Query(config, values, callback,this.connectionParameters.multipleStatementResult); if(this.binary && !query.binary) { query.binary = true; } diff --git a/lib/connection-parameters.js b/lib/connection-parameters.js index f5126a644..b48651d47 100644 --- a/lib/connection-parameters.js +++ b/lib/connection-parameters.js @@ -62,6 +62,7 @@ var ConnectionParameters = function(config) { this.application_name = val('application_name', config, 'PGAPPNAME'); this.fallback_application_name = val('fallback_application_name', config, false); + this.multipleStatementResult = val('multipleStatementResult',config); }; var add = function(params, config, paramName) { diff --git a/lib/query.js b/lib/query.js index 9e474d121..7c561e7c8 100644 --- a/lib/query.js +++ b/lib/query.js @@ -11,10 +11,10 @@ var util = require('util'); var Result = require('./result'); var utils = require('./utils'); - -var Query = function(config, values, callback) { +var configGlobal; +var Query = function (config, values, callback, multipleStatementResult) { // use of "new" optional - if(!(this instanceof Query)) { return new Query(config, values, callback); } + if (!(this instanceof Query)) { return new Query(config, values, callback); } config = utils.normalizeQueryConfig(config, values, callback); @@ -28,46 +28,53 @@ var Query = function(config, values, callback) { //use unique portal name each time this.portal = config.portal || ""; this.callback = config.callback; - if(process.domain && config.callback) { + if (process.domain && config.callback) { this.callback = process.domain.bind(config.callback); } - this._result = new Result(config.rowMode, config.types); + this.multipleStatementResult = multipleStatementResult; + if (this.multipleStatementResult == true) { + this._result = new Array(); + } + else { + this._result = new Result(config.rowMode, config.types); + } this.isPreparedStatement = false; this._canceledDueToError = false; this._promise = null; + configGlobal = config; EventEmitter.call(this); }; util.inherits(Query, EventEmitter); -Query.prototype.then = function(onSuccess, onFailure) { +Query.prototype.then = function (onSuccess, onFailure) { return this.promise().then(onSuccess, onFailure); }; -Query.prototype.catch = function(callback) { +Query.prototype.catch = function (callback) { return this.promise().catch(callback); }; -Query.prototype.promise = function() { +Query.prototype.promise = function () { if (this._promise) return this._promise; - this._promise = new Promise(function(resolve, reject) { + this._promise = new Promise(function (resolve, reject) { this.once('end', resolve); this.once('error', reject); }.bind(this)); return this._promise; }; -Query.prototype.requiresPreparation = function() { +Query.prototype.requiresPreparation = function () { //named queries must always be prepared - if(this.name) { return true; } + if (this.name) { return true; } //always prepare if there are max number of rows expected per //portal execution - if(this.rows) { return true; } + if (this.rows) { return true; } //don't prepare empty text queries - if(!this.text) { return false; } + if (!this.text) { return false; } //binary should be prepared to specify results should be in binary //unless there are no parameters - if(this.binary && !this.values) { return false; } + if (this.binary && !this.values) { return false; } //prepare if there are values return (this.values || 0).length > 0; }; @@ -76,23 +83,51 @@ Query.prototype.requiresPreparation = function() { //associates row metadata from the supplied //message with this query object //metadata used when parsing row results -Query.prototype.handleRowDescription = function(msg) { - this._result.addFields(msg.fields); +Query.prototype.handleRowDescription = function (msg) { this._accumulateRows = this.callback || !this.listeners('row').length; + if (this.multipleStatementResult == true) { + this.currentResult = new Result(configGlobal.rowMode, configGlobal.types); + this.currentResult.completed = false; + this.currentResult.addFields(msg.fields); + } + else { + this._result.addFields(msg.fields); + } }; -Query.prototype.handleDataRow = function(msg) { - var row = this._result.parseRow(msg.fields); - this.emit('row', row, this._result); - if (this._accumulateRows) { - this._result.addRow(row); +Query.prototype.handleDataRow = function (msg) { + if (this.multipleStatementResult == true) { + var row = this.currentResult.parseRow(msg.fields); + this.emit('row', row, this.currentResult); + if (this._accumulateRows) { + this.currentResult.addRow(row); + } + } + else { + var row = this._result.parseRow(msg.fields); + this.emit('row', row, this._result); + if (this._accumulateRows) { + this._result.addRow(row); + } } + }; -Query.prototype.handleCommandComplete = function(msg, con) { - this._result.addCommandComplete(msg); +Query.prototype.handleCommandComplete = function (msg, con) { + if (this.multipleStatementResult == true) { + if(this.currentResult == null || this.currentResult.completed == true){ + this.currentResult = new Result(configGlobal.rowMode, configGlobal.types); + } + this.currentResult.addCommandComplete(msg); + this._result.push(this.currentResult); + this.currentResult.completed = true; + } + else { + this._result.addCommandComplete(msg); + } + //need to sync after each command complete of a prepared statement - if(this.isPreparedStatement) { + if (this.isPreparedStatement) { con.sync(); } }; @@ -100,56 +135,56 @@ Query.prototype.handleCommandComplete = function(msg, con) { //if a named prepared statement is created with empty query text //the backend will send an emptyQuery message but *not* a command complete message //execution on the connection will hang until the backend receives a sync message -Query.prototype.handleEmptyQuery = function(con) { +Query.prototype.handleEmptyQuery = function (con) { if (this.isPreparedStatement) { con.sync(); } }; -Query.prototype.handleReadyForQuery = function() { - if(this._canceledDueToError) { +Query.prototype.handleReadyForQuery = function () { + if (this._canceledDueToError) { return this.handleError(this._canceledDueToError); } - if(this.callback) { + if (this.callback) { this.callback(null, this._result); } this.emit('end', this._result); }; -Query.prototype.handleError = function(err, connection) { +Query.prototype.handleError = function (err, connection) { //need to sync after error during a prepared statement - if(this.isPreparedStatement) { + if (this.isPreparedStatement) { connection.sync(); } - if(this._canceledDueToError) { + if (this._canceledDueToError) { err = this._canceledDueToError; this._canceledDueToError = false; } //if callback supplied do not emit error event as uncaught error //events will bubble up to node process - if(this.callback) { + if (this.callback) { return this.callback(err); } this.emit('error', err); }; -Query.prototype.submit = function(connection) { - if(this.requiresPreparation()) { +Query.prototype.submit = function (connection) { + if (this.requiresPreparation()) { this.prepare(connection); } else { connection.query(this.text); } }; -Query.prototype.hasBeenParsed = function(connection) { +Query.prototype.hasBeenParsed = function (connection) { return this.name && connection.parsedStatements[this.name]; }; -Query.prototype.handlePortalSuspended = function(connection) { +Query.prototype.handlePortalSuspended = function (connection) { this._getRows(connection, this.rows); }; -Query.prototype._getRows = function(connection, rows) { +Query.prototype._getRows = function (connection, rows) { connection.execute({ portal: this.portalName, rows: rows @@ -157,13 +192,13 @@ Query.prototype._getRows = function(connection, rows) { connection.flush(); }; -Query.prototype.prepare = function(connection) { +Query.prototype.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)) { + if (!this.hasBeenParsed(connection)) { connection.parse({ text: self.text, name: self.name, @@ -171,7 +206,7 @@ Query.prototype.prepare = function(connection) { }, true); } - if(self.values) { + if (self.values) { self.values = self.values.map(utils.prepareValue); } @@ -192,13 +227,13 @@ Query.prototype.prepare = function(connection) { }; Query.prototype.handleCopyInResponse = function (connection) { - if(this.stream) this.stream.startStreamingToConnection(connection); + if (this.stream) this.stream.startStreamingToConnection(connection); else connection.sendCopyFail('No source stream defined'); }; Query.prototype.handleCopyData = function (msg, connection) { var chunk = msg.chunk; - if(this.stream) { + if (this.stream) { this.stream.handleChunk(chunk); } //if there are no stream (for example when copy to query was sent by diff --git a/lib/result.js b/lib/result.js index 463fbdbe6..0d43d37f1 100644 --- a/lib/result.js +++ b/lib/result.js @@ -70,7 +70,10 @@ Result.prototype.parseRow = function(rowData) { }; Result.prototype.addRow = function(row) { - this.rows.push(row); + + this.rows.push(row); + + }; var inlineParser = function(fieldName, i) { diff --git a/sample/example.js b/sample/example.js new file mode 100644 index 000000000..6e9bce8c3 --- /dev/null +++ b/sample/example.js @@ -0,0 +1,33 @@ +var pg = require('../lib/index.js'); + + +var config = { + host: 'localhost', + user: 'postgres', //env var: PGUSER + database: 'postgres', //env var: PGDATABASE + password: null, //env var: PGPASSWORD + port: 5432, //env var: PGPORT + max: 10, // max number of clients in the pool + idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed + multipleStatementResult : true +}; + +var client = new pg.Client(config); + +// connect to our database +client.connect(function (err) { + if (err) throw err; + + // execute a query on our database + client.query('DELETE FROM users WHERE id = 1;SELECT * FROM users;select * from events;update users set id = 1 where id = 1; INSERT INTO users(id,email,name) values(332,\'email@example.com\',\'test user\');', function (err, result) { + if (err) throw err; + + // just print the result to the console + console.log(result.rows[0]); + + // disconnect the client + client.end(function (err) { + if (err) throw err; + }); + }); +}); \ No newline at end of file From f3f6d4e68f6e369cb3883f7ba5c8fcaca7ef97bc Mon Sep 17 00:00:00 2001 From: Can Abacigil Date: Sat, 13 Aug 2016 20:30:32 +0300 Subject: [PATCH 2/2] - unnecessary lines removed --- lib/result.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/result.js b/lib/result.js index 0d43d37f1..9a16ff013 100644 --- a/lib/result.js +++ b/lib/result.js @@ -70,10 +70,7 @@ Result.prototype.parseRow = function(rowData) { }; Result.prototype.addRow = function(row) { - this.rows.push(row); - - }; var inlineParser = function(fieldName, i) {