Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 19 commits
  • 8 files changed
  • 0 commit comments
  • 6 contributors
Commits on Jan 07, 2010
@ry ry Update API to match latest node fa35c0a
Commits on Mar 05, 2010
Devin Torres Promises are depricated. Use callbacks instead 4e40e1e
@ry ry Update tests to not use promises 1adca05
Scott McWhirter Fix the query queuing to take libpq transaction status into account f2949a8
@ry ry Callbacks should give error in first argument
And fix cast error
fa5fcd9
@ry ry Add more info to the readme 1f94827
@ry ry Add error checks to the test a03bead
Commits on Mar 06, 2010
Jérémy Lal Fix for EV_MULTIPLICITY=1 cd3bfce
Commits on Apr 09, 2010
@phoebus phoebus Escape, mapTupleFields
- a native string escaping: conn.escapeString('string')
- optional tuple-to-object mapping:
 conn.mapTupleFields = true
 causes tuple to look like: { field1: value, field2: value2 }
 instead of [ value, value2 ]
bd5f8af
Commits on Apr 15, 2010
@ry ry Fix segfault fbc40e2
Commits on May 13, 2010
@ry ry Add package.json
Fix package.json
003a83f
@ry ry Add license file e93ec04
Commits on May 21, 2010
@keithhub keithhub Add support for async notifications f769024
Commits on Jun 07, 2010
@keithhub keithhub Add test for async notification cd7e0ef
@keithhub keithhub Fix segfault in EscapeString d0dba37
Commits on Jun 08, 2010
@keithhub keithhub Replace use of deprecated sys.p 0c5462e
Commits on Jun 20, 2010
@ry ry Link to build/default instead of symlink f955d0a
@ry ry Add node-waf build to package.json 8f54a00
Commits on Aug 09, 2010
@ry ry query: args after the callback gets passed back into the callback 764ad42
View
4 .gitignore
@@ -1,2 +1,2 @@
-binding.o
-binding.node
+.lock-wscript
+build/
View
19 LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
View
29 README
@@ -4,3 +4,32 @@ or better) and do
node-waf configure build
+h2. Getting Started
+
+
+You'll need postgres to run this. Here's the easy way to do this locally:
+
+ wget http://wwwmaster.postgresql.org/redir/198/h/source/v8.4.2/postgresql-8.4.2.tar.gz
+ tar -zxf postgresql-8.4.2.tar.gz
+ cd postgresql-8.4.2
+ ./configure --prefix=$HOME/local/postgres-8.4.2 && make && make install
+ cd $HOME/local/postgres-8.4.2
+ bin/initdb ./data
+ bin/postgres -D ./data
+
+You'll probably want to add $HOME/local/postgres-8.4.2/bin to your PATH
+environment variable.
+
+ export PATH=$HOME/local/postgres-8.4.2/bin:$PATH
+
+And puts few things into the database
+
+ createdb test
+ echo "CREATE TABLE test (a int, b int); INSERT INTO test VALUES (1, 2);" | psql -d test
+
+ cd ~/src/node_postgres
+ node-waf configure
+ node-waf build
+ node test.js
+
+
View
195 binding.cc
@@ -3,11 +3,17 @@
#include <node.h>
#include <node_events.h>
#include <assert.h>
+#include <stdlib.h>
using namespace v8;
using namespace node;
-
+static Persistent<String> ready_symbol;
+static Persistent<String> result_symbol;
+static Persistent<String> close_symbol;
+static Persistent<String> connect_symbol;
+static Persistent<String> notify_symbol;
#define READY_STATE_SYMBOL String::NewSymbol("readyState")
+#define MAP_TUPLE_ITEMS_SYMBOL String::NewSymbol("mapTupleItems")
class Connection : public EventEmitter {
public:
@@ -21,12 +27,22 @@ class Connection : public EventEmitter {
t->Inherit(EventEmitter::constructor_template);
t->InstanceTemplate()->SetInternalFieldCount(1);
+ close_symbol = NODE_PSYMBOL("close");
+ connect_symbol = NODE_PSYMBOL("connect");
+ result_symbol = NODE_PSYMBOL("result");
+ ready_symbol = NODE_PSYMBOL("ready");
+ notify_symbol = NODE_PSYMBOL("notify");
+
NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect);
NODE_SET_PROTOTYPE_METHOD(t, "close", Close);
NODE_SET_PROTOTYPE_METHOD(t, "reset", Reset);
NODE_SET_PROTOTYPE_METHOD(t, "dispatchQuery", DispatchQuery);
+ NODE_SET_PROTOTYPE_METHOD(t, "escapeString", EscapeString);
t->PrototypeTemplate()->SetAccessor(READY_STATE_SYMBOL, ReadyStateGetter);
+ t->PrototypeTemplate()->SetAccessor(MAP_TUPLE_ITEMS_SYMBOL,
+ MapTupleItemsGetter,
+ MapTupleItemsSetter);
target->Set(String::NewSymbol("Connection"), t->GetFunction());
}
@@ -67,7 +83,7 @@ class Connection : public EventEmitter {
*/
ev_io_start(EV_DEFAULT_ &write_watcher_);
- Attach();
+ Ref();
return true;
}
@@ -100,7 +116,7 @@ class Connection : public EventEmitter {
if (r == 0) {
return false;
}
- if (PQflush(connection_) == 1) ev_io_start(&write_watcher_);
+ if (PQflush(connection_) == 1) ev_io_start(EV_DEFAULT_ &write_watcher_);
return true;
}
@@ -112,11 +128,11 @@ class Connection : public EventEmitter {
PQfinish(connection_);
connection_ = NULL;
if (exception.IsEmpty()) {
- Emit("close", 0, NULL);
+ Emit(close_symbol, 0, NULL);
} else {
- Emit("close", 1, &exception);
+ Emit(close_symbol, 1, &exception);
}
- Detach();
+ Unref();
}
char * ErrorMessage ( )
@@ -138,6 +154,46 @@ class Connection : public EventEmitter {
}
static Handle<Value>
+ EscapeString (const Arguments& args)
+ {
+ Connection *connection = ObjectWrap::Unwrap<Connection>(args.This());
+
+ HandleScope scope;
+
+ if (args.Length() == 0 || !args[0]->IsString()) {
+ return ThrowException(Exception::Error(
+ String::New("Must give a string to escape")));
+ }
+
+ String::Utf8Value unescaped_string(args[0]->ToString());
+
+ // the string to be escaped is a raw value and
+ // may contain everything you can imagine
+ int from_len = args[0]->ToString()->Utf8Length();
+
+ char *to;
+ to = (char *) malloc(from_len * 2 + 1);
+
+ if (connection->connection_) {
+ int error;
+ // TODO handle an error if set
+ PQescapeStringConn(connection->connection_,
+ to,
+ *unescaped_string,
+ (size_t)from_len,
+ &error);
+ } else {
+ PQescapeString(to, *unescaped_string, (size_t)from_len);
+ }
+
+ Handle<Value> result = String::New(to);
+
+ free(to);
+
+ return scope.Close(result);
+ }
+
+ static Handle<Value>
Connect (const Arguments& args)
{
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This());
@@ -145,7 +201,8 @@ class Connection : public EventEmitter {
HandleScope scope;
if (args.Length() == 0 || !args[0]->IsString()) {
- return ThrowException(String::New("Must give conninfo string as argument"));
+ return ThrowException(Exception::Error(
+ String::New("Must give conninfo string as argument")));
}
String::Utf8Value conninfo(args[0]->ToString());
@@ -211,6 +268,37 @@ class Connection : public EventEmitter {
}
static Handle<Value>
+ MapTupleItemsGetter (Local<String> property, const AccessorInfo& info)
+ {
+ Connection *connection = ObjectWrap::Unwrap<Connection>(info.This());
+ assert(connection);
+
+ assert(property == MAP_TUPLE_ITEMS_SYMBOL);
+
+ HandleScope scope;
+
+ return scope.Close(Boolean::New(connection->mapTupleItems_));
+ }
+
+ static void
+ MapTupleItemsSetter (Local<String> property,
+ Local<Value> value,
+ const AccessorInfo& info)
+ {
+ Connection *connection = ObjectWrap::Unwrap<Connection>(info.This());
+ assert(connection);
+
+ assert(property == MAP_TUPLE_ITEMS_SYMBOL);
+
+ if (!value->IsBoolean()) {
+ ThrowException(Exception::TypeError(
+ String::New("mapTupleItems should be of Boolean value")));
+ }
+
+ connection->mapTupleItems_ = value->ToBoolean()->Value();
+ }
+
+ static Handle<Value>
ReadyStateGetter (Local<String> property, const AccessorInfo& info)
{
Connection *connection = ObjectWrap::Unwrap<Connection>(info.This());
@@ -220,7 +308,26 @@ class Connection : public EventEmitter {
HandleScope scope;
- const char *s;
+ const char *s = NULL;
+
+ switch(PQtransactionStatus(connection->connection_)) {
+ case PQTRANS_IDLE:
+ s = "OK";
+ break;
+ case PQTRANS_ACTIVE:
+ s = "commandActive";
+ break;
+ case PQTRANS_UNKNOWN:
+ s = "bad";
+ break;
+ case PQTRANS_INTRANS:
+ s = "commandActiveButIdle";
+ break;
+ }
+
+ if(s != NULL) {
+ return scope.Close(String::NewSymbol(s));
+ }
switch (PQstatus(connection->connection_)) {
case CONNECTION_STARTED:
@@ -297,12 +404,12 @@ class Connection : public EventEmitter {
}
if (status == PGRES_POLLING_OK) {
- Emit("connect", 0, NULL);
+ Emit(connect_symbol, 0, NULL);
connecting_ = resetting_ = false;
ev_io_start(EV_DEFAULT_ &read_watcher_);
return;
}
-
+
CloseConnectionWithError();
}
@@ -337,7 +444,7 @@ class Connection : public EventEmitter {
default:
#ifndef NDEBUG
- printf("Unhandled OID: %d\n", t);
+// printf("Unhandled OID: %d\n", t);
#endif
cell = String::New(string);
}
@@ -351,16 +458,30 @@ class Connection : public EventEmitter {
int nrows = PQntuples(result);
int ncols = PQnfields(result);
int row_index, col_index;
+ char *field_name;
Local<Array> tuples = Array::New(nrows);
- for (row_index = 0; row_index < nrows; row_index++) {
- Local<Array> row = Array::New(ncols);
- tuples->Set(Integer::New(row_index), row);
+ if (mapTupleItems_) {
+ for (row_index = 0; row_index < nrows; row_index++) {
+ Local<Object> row = Object::New();
+ tuples->Set(Integer::New(row_index), row);
- for (col_index = 0; col_index < ncols; col_index++) {
- Local<Value> cell = BuildCell(result, row_index, col_index);
- row->Set(Integer::New(col_index), cell);
+ for (col_index = 0; col_index < ncols; col_index++) {
+ field_name = PQfname(result, col_index);
+ Local<Value> cell = BuildCell(result, row_index, col_index);
+ row->Set(String::New(field_name), cell);
+ }
+ }
+ } else {
+ for (row_index = 0; row_index < nrows; row_index++) {
+ Local<Array> row = Array::New(nrows);
+ tuples->Set(Integer::New(row_index), row);
+
+ for (col_index = 0; col_index < ncols; col_index++) {
+ Local<Value> cell = BuildCell(result, row_index, col_index);
+ row->Set(Integer::New(col_index), cell);
+ }
}
}
@@ -422,36 +543,53 @@ class Connection : public EventEmitter {
void EmitResult (PGresult *result)
{
+ HandleScope scope;
+
Local<Value> tuples;
Local<Value> exception;
+ Local<Value> args[2];
+
switch (PQresultStatus(result)) {
case PGRES_EMPTY_QUERY:
case PGRES_COMMAND_OK:
- Emit("result", 0, NULL);
+ Emit(result_symbol, 0, NULL);
break;
case PGRES_TUPLES_OK:
- tuples = BuildTuples(result);
- Emit("result", 1, &tuples);
+ args[0] = Local<Value>::New(Null());
+ args[1] = BuildTuples(result);
+ Emit(result_symbol, 2, args);
break;
case PGRES_COPY_OUT:
case PGRES_COPY_IN:
assert(0 && "Not yet implemented.");
exception = Exception::Error(String::New("Not yet implemented"));
- Emit("result", 1, &exception);
+ Emit(result_symbol, 1, &exception);
break;
case PGRES_BAD_RESPONSE:
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
exception = BuildResultException(result);
- Emit("result", 1, &exception);
+ Emit(result_symbol, 1, &exception);
break;
}
}
+ void EmitNotify (PGnotify *notify)
+ {
+ HandleScope scope;
+
+ Local<Value> args[3];
+
+ args[0] = Local<Value>::New(String::New(notify->relname));
+ args[1] = Local<Value>::New(Integer::New(notify->be_pid));
+ args[2] = Local<Value>::New(String::New(notify->extra));
+ Emit(notify_symbol, 3, args);
+ }
+
void Event (int revents)
{
if (revents & EV_ERROR) {
@@ -478,12 +616,18 @@ class Connection : public EventEmitter {
EmitResult(result);
PQclear(result);
}
- Emit("ready", 0, NULL);
- }
+ Emit(ready_symbol, 0, NULL);
+ }
+
+ PGnotify *notify;
+ while ((notify = PQnotifies(connection_))) {
+ EmitNotify(notify);
+ PQfreemem(notify);
+ }
}
if (revents & EV_WRITE) {
- if (PQflush(connection_) == 0) ev_io_stop(&write_watcher_);
+ if (PQflush(connection_) == 0) ev_io_stop(EV_DEFAULT_ &write_watcher_);
}
}
@@ -499,6 +643,7 @@ class Connection : public EventEmitter {
PGconn *connection_;
bool connecting_;
bool resetting_;
+ bool mapTupleItems_;
};
extern "C" void
View
7 package.json
@@ -0,0 +1,7 @@
+{ "name" : "postgres"
+, "version" : "0.0.1"
+, "description" : "very basic libpg binding to node"
+, "author": "Ryan Dahl"
+, "main": "./postgres"
+, "scripts": { "install": "node-waf configure build" }
+}
View
32 postgres.js
@@ -1,4 +1,4 @@
-var binding = require("./binding");
+var binding = require("./build/default/binding");
var Connection = binding.Connection;
@@ -11,17 +11,14 @@ Connection.prototype.maybeDispatchQuery = function () {
if (this.readyState != "OK") return;
if (!this.currentQuery && this._queries.length > 0) {
this.currentQuery = this._queries.shift();
- this.dispatchQuery(this.currentQuery.sql);
+ this.dispatchQuery(this.currentQuery[0]);
}
};
-Connection.prototype.query = function (sql) {
- if (!this._queries) this._queries = [];
- var promise = new process.Promise;
- promise.sql = sql;
- this._queries.push(promise);
+Connection.prototype.query = function (sql, callback) {
+ this._queries = this._queries || [];
+ this._queries.push([sql, callback, arguments]);
this.maybeDispatchQuery();
- return promise;
};
exports.createConnection = function (conninfo) {
@@ -31,14 +28,21 @@ exports.createConnection = function (conninfo) {
c.maybeDispatchQuery();
});
- c.addListener("result", function (arg) {
+ c.addListener("result", function () {
process.assert(c.currentQuery);
- var promise = c.currentQuery;
+ var callback = c.currentQuery[1];
+ var args = Array.prototype.slice.call(c.currentQuery[2]);
+ args.shift();
+ args.shift();
c.currentQuery = null;
- if (arg instanceof Error) {
- promise.emitError([arg]);
- } else {
- promise.emitSuccess([arg]);
+ if (callback) {
+ var newArgs = Array.prototype.slice.call(arguments);
+
+ for(var i = 0; i < args.length; i++) {
+ newArgs.push(args[i]);
+ }
+
+ callback.apply(c, newArgs);
}
});
View
92 test.js
@@ -1,36 +1,98 @@
-process.mixin(GLOBAL, require("sys"));
-var postgres = require("./postgres");
+var postgres = require('./postgres'),
+ sys = require('sys'),
+ puts = sys.puts;
-var c = postgres.createConnection("host=localhost dbname=ryan");
+var p = function () {
+ puts(sys.inspect.apply(this, arguments));
+}
+
+var c = postgres.createConnection("host='' dbname=test");
+
+puts(c.escapeString("e's'c'a'p'e me"));
+
+puts('map tuple items: ' + sys.inspect(c.mapTupleItems))
+c.mapTupleItems = false
+puts('map tuple items is turned off and now: ' + sys.inspect(c.mapTupleItems))
+
+try {
+ c.mapTupleItems = "x";
+ puts('broken behaviour: mapTupleItems accepted a string, but should reject it')
+}
+catch (e) {
+ puts('map tuple items rejects non-boolean values')
+}
c.addListener("connect", function () {
puts("connected");
puts(c.readyState);
+
+ puts(c.escapeString("e's'c'a'p'e UTF8 too: ±—°."));
});
-c.addListener("close", function (e) {
+c.addListener("close", function (err) {
puts("connection closed.");
- if (e) {
- puts("error: " + e.message);
- }
+ if (err) puts("error: " + err.message);
});
-c.query("select * from test;").addCallback(function (rows) {
+c.query("select * from test;", function (err, rows) {
+ if (err) throw err;
puts("result1:");
p(rows);
});
-c.query("select * from test limit 1;").addCallback(function (rows) {
+c.mapTupleItems = true;
+c.query("select * from test;", function (err, rows) {
+ if (err) throw err;
+ puts("result1.1:");
+ p(rows);
+});
+
+c.query("select * from test limit 1;", function (err, rows) {
+ if (err) throw err;
puts("result2:");
p(rows);
});
-c.query("select ____ from test limit 1;").addCallback(function (rows) {
+c.query("select ____ from test limit 1;", function (err, rows) {
+ if (err) {
+ puts("error! "+ err.message);
+ puts("full: "+ err.full);
+ puts("severity: "+ err.severity);
+ }
puts("result3:");
p(rows);
-}).addErrback(function (e) {
- puts("error! "+ e.message);
- puts("full: "+ e.full);
- puts("severity: "+ e.severity);
- c.close();
+});
+
+c.query("select * from test;", function (err, rows) {
+ c.query("select * from test;", function (err, rows) {
+ puts("result4:");
+ p(rows);
+ });
+});
+
+c.query("listen testnotice;", function () {
+ var timeout = setTimeout(function () {
+ puts("timeout waiting for notification");
+ c.close();
+ }, 2000);
+
+ c.addListener("notify", function (relname, pid, extras) {
+ if (relname === "testnotice") {
+ puts("got notification from " + pid + ", extras:" + extras);
+ clearTimeout(timeout);
+ c.close();
+ }
+ });
+
+ c.query("notify testnotice;");
+});
+
+c.query("select * from test;", function (err, rows) {
+ for(var i = 0; i < rows.length; i++) {
+ var currentrow = rows[i];
+ c.query("select * from test;", function(err, rows, related) {
+ puts(related); // row from the iteration
+ puts(rows); // rows from the query
+ }, currentrow);
+ }
});
View
9 wscript
@@ -26,12 +26,3 @@ def build(bld):
obj.source = "binding.cc"
obj.uselib = "PG"
-
-def shutdown():
- # HACK to get binding.node out of build directory.
- # better way to do this?
- if Options.commands['clean']:
- if exists('binding.node'): unlink('binding.node')
- else:
- if exists('build/default/binding.node') and not exists('binding.node'):
- symlink('build/default/binding.node', 'binding.node')

No commit comments for this range

Something went wrong with that request. Please try again.