Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
brianc committed Sep 2, 2014
0 parents commit 7efb2e0
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.PHONY: publish-patch test

test:
npm test

publish-patch: test
npm version patch -m "Bump version"
git push origin master --tags
npm publish
Empty file added README.md
Empty file.
54 changes: 54 additions & 0 deletions bench/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
var pg = require('pg').native
var Native = require('../')

var warmup = function(fn, cb) {
var count = 0;
var max = 10;
var run = function(err) {
if(err) return cb(err);

if(max >= count++) {
return fn(run)
}

cb()
}
run();
}

var native = Native();
native.connectSync();

var queryText = 'SELECT generate_series(0, 1000)';
var client = new pg.Client();
client.connect(function() {
var pure = function(cb) {
client.query(queryText, function(err) {
if(err) throw err;
cb(err);
});
}
var nativeQuery = function(cb) {
native.query(queryText, function(err) {
if(err) throw err;
cb(err);
});
}

var run = function() {
var start = Date.now()
warmup(pure, function() {
console.log('pure done', Date.now() - start)
start = Date.now()
warmup(nativeQuery, function() {
console.log('native done', Date.now() - start)
})
})
}

setInterval(function() {
run()
}, 500)

});

124 changes: 124 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
var Libpq = require('libpq');

var Client = module.exports = function(types) {
if(!types) {
var pgTypes = require('pg-types')
types = pgTypes.getTypeParser.bind(pgTypes)
}
if(!(this instanceof Client)) {
return new Client(types);
}
this.pq = new Libpq();
this.types = types;
};

Client.prototype.connect = function(params, cb) {
this.pq.connect(params, cb);
};

Client.prototype.connectSync = function(params) {
this.pq.connectSync(params);
};

Client.prototype.end = function(cb) {
this.pq.finish();
if(cb) setImmediate(cb);
};

var mapResults = function(pq, types) {
var rows = [];
var rowCount = pq.ntuples();
var colCount = pq.nfields();
for(var i = 0; i < rowCount; i++) {
var row = {};
rows.push(row);
for(var j = 0; j < colCount; j++) {
var rawValue = pq.getvalue(i, j);
var value = rawValue;
if(rawValue == '') {
if(pq.getisnull()) {
value = null;
}
} else {
value = types(pq.ftype(j))(rawValue);
}
row[pq.fname(j)] = value;
}
}
return rows;
};

var dispatchQuery = function(pq, fn, cb) {
setImmediate(function() {
var sent = fn();
if(!sent) return cb(new Error(pq.errorMessage()));
cb();
});
};

var consumeResults = function(pq, cb) {
var cleanup = function() {
pq.removeListener('readable', onReadable);
pq.stopReader();
}

var readError = function(message) {
cleanup();
return cb(new Error(message || pq.errorMessage));
};

var onReadable = function() {
//read waiting data from the socket
//e.g. clear the pending 'select'
if(!pq.consumeInput()) {
return readError();
}
//check if there is still outstanding data
//if so, wait for it all to come in
if(pq.isBusy()) {
return;
}
//load our result object
pq.getResult();

//"read until results return null"
//or in our case ensure we only have one result
if(pq.getResult()) {
return readError('Only one result at a time is accepted');
}
cleanup();
return cb(null);
};
pq.on('readable', onReadable);
pq.startReader();
};

Client.prototype.query = function(text, values, cb) {
var queryFn;
var pq = this.pq
var types = this.types
if(typeof values == 'function') {
cb = values;
queryFn = pq.sendQuery.bind(pq, text);
} else {
queryFn = pq.sendQueryParams.bind(pq, text, values);
}

dispatchQuery(pq, queryFn, function(err) {
if(err) return cb(err);
consumeResults(pq, function(err) {
return cb(err, err ? null : mapResults(pq, types));
});
});
};

Client.prototype.querySync = function(text, values) {
var queryFn;
var pq = this.pq;
pq[values ? 'execParams' : 'exec'].call(pq, text, values);
var success = !pq.errorMessage();
if(!success) {
throw new Error(pq.resultErrorMessage());
}
return mapResults(pq, this.types);
};
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "pg-native",
"version": "0.0.0",
"description": "A slightly nicer interface to Postgres over node-libpq",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-pg-native.git"
},
"keywords": [
"postgres",
"pg",
"libpq"
],
"author": "Brian M. Carlson",
"license": "MIT",
"bugs": {
"url": "https://github.com/brianc/node-pg-native/issues"
},
"homepage": "https://github.com/brianc/node-pg-native",
"dependencies": {
"libpq": "^0.2.0",
"pg-types": "^1.4.0"
},
"devDependencies": {
"mocha": "^1.21.4",
"async": "^0.9.0"
}
}
93 changes: 93 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
var Client = require('../')
var assert = require('assert')
var async = require('async')

describe('connection', function() {
it('works', function(done) {
Client().connect(done);
});

it('connects with args', function(done) {
Client().connect('host=localhost', done);
});

it('errors out with bad connection args', function(done) {
Client().connect('host=asldkfjasdf', function(err) {
assert(err, 'should raise an error for bad host');
done();
});
});
});

describe('connectSync', function() {
it('works without args', function() {
Client().connectSync();
});

it('works with args', function() {
Client().connectSync('host=localhost');
});

it('throws if bad host', function() {
assert.throws(function() {
Client().connectSync('host=laksdjfdsf');
});
});
});

describe('async query', function() {
before(function(done) {
this.client = Client();
this.client.connect(function(err) {
if(err) return done(err);
return done();
});
});

after(function(done) {
this.client.end(done);
});

it('simple query works', function(done) {
var runQuery = function(n, done) {
this.client.query('SELECT NOW() AS the_time', function(err, rows) {
if(err) return done(err);
assert.equal(rows[0].the_time.getFullYear(), new Date().getFullYear());
return done();
});
}.bind(this);
async.timesSeries(3, runQuery, done)
});

it('parameters work', function(done) {
var runQuery = function(n, done) {
this.client.query('SELECT $1::text AS name', ['Brian'], done);
}.bind(this);
async.timesSeries(3, runQuery, done)
});

it('prepared, named statements work');
});

describe('query sync', function(done) {
before(function() {
this.client = Client();
this.client.connectSync();
});

after(function(done) {
this.client.end(done);
});

it('simple query works', function() {
var rows = this.client.querySync('SELECT NOW() AS the_time');
assert.equal(rows.length, 1);
assert.equal(rows[0].the_time.getFullYear(), new Date().getFullYear());
});

it('parameterized query works', function() {
var rows = this.client.querySync('SELECT $1::text AS name', ['Brian']);
assert.equal(rows.length, 1);
assert.equal(rows[0].name, 'Brian');
});
});
2 changes: 2 additions & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--bail
--no-exit

0 comments on commit 7efb2e0

Please sign in to comment.