2010-10-23 07:16:40 +08:00
|
|
|
var crypto = require('crypto');
|
2010-10-24 01:46:27 +08:00
|
|
|
var EventEmitter = require('events').EventEmitter;
|
2011-10-11 08:40:52 +08:00
|
|
|
var util = require('util');
|
2011-02-05 10:05:02 +08:00
|
|
|
|
2010-11-03 13:27:11 +08:00
|
|
|
var Query = require(__dirname + '/query');
|
2010-10-24 01:45:37 +08:00
|
|
|
var utils = require(__dirname + '/utils');
|
2010-11-21 04:09:18 +08:00
|
|
|
var defaults = require(__dirname + '/defaults');
|
2010-10-24 08:02:13 +08:00
|
|
|
var Connection = require(__dirname + '/connection');
|
2010-10-24 01:46:27 +08:00
|
|
|
|
2010-10-11 11:37:30 +08:00
|
|
|
var Client = function(config) {
|
|
|
|
EventEmitter.call(this);
|
2010-11-11 11:45:39 +08:00
|
|
|
if(typeof config === 'string') {
|
2011-03-02 04:35:14 +08:00
|
|
|
config = utils.normalizeConnectionInfo(config)
|
2010-11-11 11:45:39 +08:00
|
|
|
}
|
2010-10-11 11:37:30 +08:00
|
|
|
config = config || {};
|
2010-11-21 04:09:18 +08:00
|
|
|
this.user = config.user || defaults.user;
|
|
|
|
this.database = config.database || defaults.database;
|
|
|
|
this.port = config.port || defaults.port;
|
|
|
|
this.host = config.host || defaults.host;
|
2011-02-05 10:05:02 +08:00
|
|
|
this.connection = config.connection || new Connection({stream: config.stream});
|
2010-10-11 11:37:30 +08:00
|
|
|
this.queryQueue = [];
|
2010-11-21 04:09:18 +08:00
|
|
|
this.password = config.password || defaults.password;
|
2011-11-21 18:45:55 +08:00
|
|
|
this.binary = config.binary || defaults.binary;
|
2010-10-20 13:34:16 +08:00
|
|
|
this.encoding = 'utf8';
|
2011-11-02 23:07:14 +08:00
|
|
|
this.processID = null;
|
|
|
|
this.secretKey = null;
|
2011-03-04 12:44:31 +08:00
|
|
|
var self = this;
|
2010-10-11 11:37:30 +08:00
|
|
|
};
|
|
|
|
|
2011-10-11 08:40:52 +08:00
|
|
|
util.inherits(Client, EventEmitter);
|
2010-10-11 11:37:30 +08:00
|
|
|
|
2010-10-11 11:44:13 +08:00
|
|
|
var p = Client.prototype;
|
|
|
|
|
2011-09-21 23:41:07 +08:00
|
|
|
p.connect = function(callback) {
|
2010-10-11 11:37:30 +08:00
|
|
|
var self = this;
|
2010-10-24 08:02:13 +08:00
|
|
|
var con = this.connection;
|
2011-03-02 04:35:14 +08:00
|
|
|
if(this.host && this.host.indexOf('/') === 0) {
|
|
|
|
con.connect(this.host + '/.s.PGSQL.' + this.port);
|
|
|
|
} else {
|
|
|
|
con.connect(this.port, this.host);
|
|
|
|
}
|
|
|
|
|
2010-10-24 09:26:24 +08:00
|
|
|
|
2010-10-25 11:52:12 +08:00
|
|
|
//once connection is established send startup message
|
2010-10-24 09:26:24 +08:00
|
|
|
con.on('connect', function() {
|
2010-10-25 10:28:10 +08:00
|
|
|
con.startup({
|
2010-10-24 09:26:24 +08:00
|
|
|
user: self.user,
|
|
|
|
database: self.database
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2010-10-25 11:52:12 +08:00
|
|
|
//password request handling
|
2010-10-24 08:02:13 +08:00
|
|
|
con.on('authenticationCleartextPassword', function() {
|
2010-10-25 10:26:18 +08:00
|
|
|
con.password(self.password);
|
2010-10-20 12:29:23 +08:00
|
|
|
});
|
|
|
|
|
2010-10-25 11:52:12 +08:00
|
|
|
//password request handling
|
2010-10-24 08:21:01 +08:00
|
|
|
con.on('authenticationMD5Password', function(msg) {
|
2010-10-24 09:26:24 +08:00
|
|
|
var inner = Client.md5(self.password + self.user);
|
|
|
|
var outer = Client.md5(inner + msg.salt.toString('binary'));
|
2010-10-24 08:21:01 +08:00
|
|
|
var md5password = "md5" + outer;
|
2010-10-25 10:26:18 +08:00
|
|
|
con.password(md5password);
|
2010-10-23 07:16:40 +08:00
|
|
|
});
|
2010-10-24 08:21:01 +08:00
|
|
|
|
2011-11-02 23:07:14 +08:00
|
|
|
con.once('backendKeyData', function(msg) {
|
2012-03-23 04:32:56 +08:00
|
|
|
self.processID = msg.processID;
|
|
|
|
self.secretKey = msg.secretKey;
|
|
|
|
});
|
2011-11-02 23:07:14 +08:00
|
|
|
|
2011-02-05 10:03:41 +08:00
|
|
|
//hook up query handling events to connection
|
|
|
|
//after the connection initially becomes ready for queries
|
2011-02-05 08:51:23 +08:00
|
|
|
con.once('readyForQuery', function() {
|
2011-02-05 10:03:41 +08:00
|
|
|
//delegate row descript to active query
|
2011-02-05 09:03:23 +08:00
|
|
|
con.on('rowDescription', function(msg) {
|
|
|
|
self.activeQuery.handleRowDescription(msg);
|
|
|
|
});
|
2011-02-05 10:03:41 +08:00
|
|
|
//delegate datarow to active query
|
2011-02-05 09:03:23 +08:00
|
|
|
con.on('dataRow', function(msg) {
|
|
|
|
self.activeQuery.handleDataRow(msg);
|
|
|
|
});
|
2011-02-05 10:03:41 +08:00
|
|
|
//TODO should query gain access to connection?
|
2011-02-05 09:30:30 +08:00
|
|
|
con.on('portalSuspended', function(msg) {
|
|
|
|
self.activeQuery.getRows(con);
|
|
|
|
});
|
|
|
|
|
2011-02-05 09:03:23 +08:00
|
|
|
con.on('commandComplete', function(msg) {
|
2011-02-05 10:03:41 +08:00
|
|
|
//delegate command complete to query
|
2011-02-05 09:03:23 +08:00
|
|
|
self.activeQuery.handleCommandComplete(msg);
|
2011-02-05 09:30:30 +08:00
|
|
|
//need to sync after each command complete of a prepared statement
|
|
|
|
if(self.activeQuery.isPreparedStatement) {
|
|
|
|
con.sync();
|
|
|
|
}
|
2011-02-05 09:15:57 +08:00
|
|
|
});
|
2011-03-05 04:04:59 +08:00
|
|
|
|
2011-09-21 23:41:07 +08:00
|
|
|
if (!callback) {
|
|
|
|
self.emit('connect');
|
|
|
|
} else {
|
|
|
|
callback(null,self);
|
|
|
|
//remove callback for proper error handling after the connect event
|
|
|
|
callback = null;
|
|
|
|
}
|
2011-02-05 09:15:57 +08:00
|
|
|
|
2011-03-05 03:30:19 +08:00
|
|
|
con.on('notification', function(msg) {
|
|
|
|
self.emit('notification', msg);
|
2011-09-21 23:41:07 +08:00
|
|
|
});
|
2011-03-05 03:30:19 +08:00
|
|
|
|
2011-02-05 10:03:41 +08:00
|
|
|
});
|
2011-02-05 08:51:23 +08:00
|
|
|
|
2011-02-05 10:03:41 +08:00
|
|
|
con.on('readyForQuery', function() {
|
|
|
|
if(self.activeQuery) {
|
|
|
|
self.activeQuery.handleReadyForQuery();
|
|
|
|
}
|
2012-05-08 22:07:43 +08:00
|
|
|
self.activeQuery = null;
|
2011-02-05 10:03:41 +08:00
|
|
|
self.readyForQuery = true;
|
2011-02-05 10:07:59 +08:00
|
|
|
self._pulseQueryQueue();
|
2010-10-25 11:52:12 +08:00
|
|
|
});
|
2010-10-26 14:51:12 +08:00
|
|
|
|
|
|
|
con.on('error', function(error) {
|
2010-11-15 06:50:38 +08:00
|
|
|
if(!self.activeQuery) {
|
2011-09-21 23:41:07 +08:00
|
|
|
if(!callback) {
|
|
|
|
self.emit('error', error);
|
|
|
|
} else {
|
|
|
|
callback(error);
|
|
|
|
}
|
2011-02-05 09:15:57 +08:00
|
|
|
} else {
|
2011-02-05 09:30:30 +08:00
|
|
|
//need to sync after error during a prepared statement
|
|
|
|
if(self.activeQuery.isPreparedStatement) {
|
|
|
|
con.sync();
|
|
|
|
}
|
2011-02-05 09:45:30 +08:00
|
|
|
self.activeQuery.handleError(error);
|
2011-02-05 09:15:57 +08:00
|
|
|
self.activeQuery = null;
|
2010-11-15 06:50:38 +08:00
|
|
|
}
|
2010-10-26 14:51:12 +08:00
|
|
|
});
|
2011-03-04 07:30:17 +08:00
|
|
|
|
|
|
|
con.on('notice', function(msg) {
|
|
|
|
self.emit('notice', msg);
|
2011-09-21 23:41:07 +08:00
|
|
|
});
|
2011-03-04 07:30:17 +08:00
|
|
|
|
2010-10-24 02:12:49 +08:00
|
|
|
};
|
|
|
|
|
2011-11-02 23:07:14 +08:00
|
|
|
p.cancel = function(client, query) {
|
2012-03-23 04:32:56 +08:00
|
|
|
if (client.activeQuery == query) {
|
|
|
|
var con = this.connection;
|
|
|
|
|
|
|
|
if(this.host && this.host.indexOf('/') === 0) {
|
|
|
|
con.connect(this.host + '/.s.PGSQL.' + this.port);
|
|
|
|
} else {
|
|
|
|
con.connect(this.port, this.host);
|
|
|
|
}
|
|
|
|
|
|
|
|
//once connection is established send cancel message
|
|
|
|
con.on('connect', function() {
|
|
|
|
con.cancel(client.processID, client.secretKey);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (client.queryQueue.indexOf(query) != -1)
|
|
|
|
client.queryQueue.splice(client.queryQueue.indexOf(query), 1);
|
2011-11-02 23:07:14 +08:00
|
|
|
};
|
|
|
|
|
2011-02-05 10:07:59 +08:00
|
|
|
p._pulseQueryQueue = function() {
|
2010-10-30 08:48:31 +08:00
|
|
|
if(this.readyForQuery===true) {
|
2011-02-05 10:03:41 +08:00
|
|
|
this.activeQuery = this.queryQueue.shift();
|
|
|
|
if(this.activeQuery) {
|
2010-10-30 08:48:31 +08:00
|
|
|
this.readyForQuery = false;
|
2010-12-03 07:47:54 +08:00
|
|
|
this.hasExecuted = true;
|
2011-02-05 10:03:41 +08:00
|
|
|
this.activeQuery.submit(this.connection);
|
2010-12-03 07:47:54 +08:00
|
|
|
} else if(this.hasExecuted) {
|
2010-12-11 06:56:10 +08:00
|
|
|
this.activeQuery = null;
|
2011-08-30 12:43:36 +08:00
|
|
|
this._drainPaused > 0 ? this._drainPaused++ : this.emit('drain')
|
2010-10-30 08:48:31 +08:00
|
|
|
}
|
2010-10-11 11:37:30 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-11-15 14:42:38 +08:00
|
|
|
p.query = function(config, values, callback) {
|
2010-10-28 07:58:58 +08:00
|
|
|
//can take in strings or config objects
|
2011-11-21 18:42:23 +08:00
|
|
|
config = (typeof(config) == 'string') ? { text: config } : config;
|
2011-11-21 18:45:55 +08:00
|
|
|
if (this.binary && !('binary' in config)) {
|
|
|
|
config.binary = true;
|
|
|
|
}
|
2011-02-05 09:45:30 +08:00
|
|
|
|
2010-11-15 14:42:38 +08:00
|
|
|
if(values) {
|
|
|
|
if(typeof values === 'function') {
|
|
|
|
callback = values;
|
2011-02-05 09:45:30 +08:00
|
|
|
} else {
|
2010-11-15 14:42:38 +08:00
|
|
|
config.values = values;
|
|
|
|
}
|
|
|
|
}
|
2011-02-05 09:45:30 +08:00
|
|
|
|
|
|
|
config.callback = callback;
|
2010-11-15 14:42:38 +08:00
|
|
|
|
2010-11-15 14:10:21 +08:00
|
|
|
var query = new Query(config);
|
2010-10-25 12:32:18 +08:00
|
|
|
this.queryQueue.push(query);
|
2011-02-05 10:07:59 +08:00
|
|
|
this._pulseQueryQueue();
|
2010-10-25 12:32:18 +08:00
|
|
|
return query;
|
2010-10-24 02:12:49 +08:00
|
|
|
};
|
|
|
|
|
2011-08-30 12:43:36 +08:00
|
|
|
//prevents client from otherwise emitting 'drain' event until 'resumeDrain' is called
|
|
|
|
p.pauseDrain = function() {
|
|
|
|
this._drainPaused = 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
//resume raising 'drain' event
|
|
|
|
p.resumeDrain = function() {
|
|
|
|
if(this._drainPaused > 1) {
|
|
|
|
this.emit('drain');
|
|
|
|
}
|
|
|
|
this._drainPaused = 0;
|
|
|
|
};
|
|
|
|
|
2010-10-26 10:24:17 +08:00
|
|
|
p.end = function() {
|
|
|
|
this.connection.end();
|
|
|
|
};
|
|
|
|
|
2010-10-25 11:52:12 +08:00
|
|
|
Client.md5 = function(string) {
|
|
|
|
return crypto.createHash('md5').update(string).digest('hex');
|
2010-10-24 02:12:49 +08:00
|
|
|
};
|
|
|
|
|
2010-10-11 11:37:30 +08:00
|
|
|
module.exports = Client;
|