From 00fb47ac3051538162b830ae7addc1a98e89699e Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Mon, 30 Nov 2009 18:29:56 -0800 Subject: [PATCH 01/13] Fixed the demo changed .markdown to .md --- README.markdown | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 README.markdown diff --git a/README.markdown b/README.markdown deleted file mode 100644 index a404f29..0000000 --- a/README.markdown +++ /dev/null @@ -1,19 +0,0 @@ -# PostgreSQL for Javascript - -This library is a implementation of the PostgreSQL backend/frontend protocol in javascript. -It uses the node.js tcp and event libraries. A javascript md5 library is included for servers that require md5 password hashing (this is default). - -## Example use - - var sys = require("sys"); - var Postgres = require('postgres.js'); - - function onLoad() { - var db = new Postgres.Connection("database", "username", "password"); - db.query("SELECT * FROM sometable", function (data) { - sys.p(data); - }); - db.close(); - } - - From 41c6cf1ca4281cdbea9de7afb78e685f470f7377 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Mon, 30 Nov 2009 18:34:14 -0800 Subject: [PATCH 02/13] Forgot to actually add my changes :( --- README.md | 16 ++++++++++++++++ demo.js | 8 ++++++++ 2 files changed, 24 insertions(+) create mode 100644 README.md create mode 100644 demo.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd907a3 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# PostgreSQL for Javascript + +This library is a implementation of the PostgreSQL backend/frontend protocol in javascript. +It uses the node.js tcp and event libraries. A javascript md5 library is included for servers that require md5 password hashing (this is default). + +## Example use + + var sys = require("sys"); + var Postgres = require("./postgres"); + + var db = new Postgres.Connection("database", "username", "password"); + db.query("SELECT * FROM sometable", function (data) { + sys.p(data); + }); + db.close(); + diff --git a/demo.js b/demo.js new file mode 100644 index 0000000..efb9536 --- /dev/null +++ b/demo.js @@ -0,0 +1,8 @@ +var sys = require("sys"); +var Postgres = require("./postgres"); + +var db = new Postgres.Connection("database", "username", "password"); +db.query("SELECT * FROM sometable", function (data) { + sys.p(data); +}); +db.close(); From c4c344b570a8e5f84ba0f30fb80cc97474e61f08 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Mon, 30 Nov 2009 22:20:37 -0800 Subject: [PATCH 03/13] Used ry's type-oids header to build one for javascript. * starting to write more parsing functions --- demo.js | 1 + postgres-js/bits.js | 7 +++++ postgres-js/type-oids.js | 67 ++++++++++++++++++++++++++++++++++++++++ postgres.js | 15 +++++---- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 postgres-js/type-oids.js diff --git a/demo.js b/demo.js index efb9536..7b79cd8 100644 --- a/demo.js +++ b/demo.js @@ -1,5 +1,6 @@ var sys = require("sys"); var Postgres = require("./postgres"); +Postgres.DEBUG = 1; var db = new Postgres.Connection("database", "username", "password"); db.query("SELECT * FROM sometable", function (data) { diff --git a/postgres-js/bits.js b/postgres-js/bits.js index b585eeb..e8c36da 100644 --- a/postgres-js/bits.js +++ b/postgres-js/bits.js @@ -95,3 +95,10 @@ String.prototype.parse = function () { return [pos, data]; }; +exports.parseDate = function (str) { + var parts = str.split(' '); + var date = parts[0].split('-'); + var time = parts[1].split(':'); + + var timestamp = date. +} diff --git a/postgres-js/type-oids.js b/postgres-js/type-oids.js new file mode 100644 index 0000000..cf46eb7 --- /dev/null +++ b/postgres-js/type-oids.js @@ -0,0 +1,67 @@ +// taken from ry's node_postgres module + +exports.BOOL = 16 +exports.BYTEA = 17 +exports.CHAR = 18 +exports.NAME = 19 +exports.INT8 = 20 +exports.INT2 = 21 +exports.INT2VECTOR = 22 +exports.INT4 = 23 +exports.REGPROC = 24 +exports.TEXT = 25 +exports.OID = 26 +exports.TID = 27 +exports.XID = 28 +exports.CID = 29 +exports.VECTOROID = 30 +exports.PG_TYPE_RELTYPE_ = 71 +exports.PG_ATTRIBUTE_RELTYPE_ = 75 +exports.PG_PROC_RELTYPE_ = 81 +exports.PG_CLASS_RELTYPE_ = 83 +exports.POINT = 600 +exports.LSEG = 601 +exports.PATH = 602 +exports.BOX = 603 +exports.POLYGON = 604 +exports.LINE = 628 +exports.FLOAT4 = 700 +exports.FLOAT8 = 701 +exports.ABSTIME = 702 +exports.RELTIME = 703 +exports.TINTERVAL = 704 +exports.UNKNOWN = 705 +exports.CIRCLE = 718 +exports.CASH = 790 +exports.MACADDR = 829 +exports.INET = 869 +exports.CIDR = 650 +exports.INT4ARRAY = 1007 +exports.ACLITEM = 1033 +exports.BPCHAR = 1042 +exports.VARCHAR = 1043 +exports.DATE = 1082 +exports.TIME = 1083 +exports.TIMESTAMP = 1114 +exports.TIMESTAMPTZ = 1184 +exports.INTERVAL = 1186 +exports.TIMETZ = 1266 +exports.BIT = 1560 +exports.VARBIT = 1562 +exports.NUMERIC = 1700 +exports.REFCURSOR = 1790 +exports.REGPROCEDURE = 2202 +exports.REGOPER = 2203 +exports.REGOPERATOR = 2204 +exports.REGCLASS = 2205 +exports.REGTYPE = 2206 +exports.RECORD = 2249 +exports.CSTRING = 2275 +exports.ANY = 2276 +exports.ANYARRAY = 2277 +exports.VOID = 2278 +exports.TRIGGER = 2279 +exports.LANGUAGE_HANDLER = 2280 +exports.INTERNAL = 2281 +exports.OPAQUE = 2282 +exports.ANYELEMENT = 2283 diff --git a/postgres.js b/postgres.js index 7488307..cfb75c0 100644 --- a/postgres.js +++ b/postgres.js @@ -5,6 +5,7 @@ process.mixin(require('./postgres-js/bits')); process.mixin(require('./postgres-js/md5')); var tcp = require("tcp"); var sys = require("sys"); +var oid = require("./postgres-js/type-oids.js"); exports.DEBUG = 0; @@ -187,14 +188,14 @@ function parse_response(code, stream) { } -exports.Connection = function (database, username, password, port) { +exports.Connection = function (database, username, password, port, host) { // Default to port 5432 if (port === undefined) { port = 5432; } - var connection = tcp.createConnection(port); + var connection = tcp.createConnection(port, host); var events = new process.EventEmitter(); var query_queue = []; var row_description; @@ -294,14 +295,16 @@ exports.Connection = function (database, username, password, port) { // TODO: investigate to see if these numbers are stable across databases or // if we need to dynamically pull them from the pg_types table switch (description.type_id) { - case 16: // bool + case oid.BOOL: value = value === 't'; break; - case 20: // int8 - case 21: // int2 - case 23: // int4 + case oid.INT8: + case oid.INT2: + case oid.INT4: value = parseInt(value, 10); break; + case oid.DATE: // date + value = parseDate(value); } } row[description.field] = value; From 04ce9a6926bcf6f9ee83860b7e72210bd452fb78 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 2 Dec 2009 17:49:53 -0800 Subject: [PATCH 04/13] Added date parsing and date formatting --- demo.js | 1 - postgres-js/bits.js | 9 --- postgres-js/parsers.js | 77 ++++++++++++++++++++++ postgres-js/type-oids.js | 134 +++++++++++++++++++-------------------- postgres.js | 22 +++++-- 5 files changed, 162 insertions(+), 81 deletions(-) create mode 100644 postgres-js/parsers.js diff --git a/demo.js b/demo.js index 7b79cd8..efb9536 100644 --- a/demo.js +++ b/demo.js @@ -1,6 +1,5 @@ var sys = require("sys"); var Postgres = require("./postgres"); -Postgres.DEBUG = 1; var db = new Postgres.Connection("database", "username", "password"); db.query("SELECT * FROM sometable", function (data) { diff --git a/postgres-js/bits.js b/postgres-js/bits.js index 57b741f..4914bdd 100644 --- a/postgres-js/bits.js +++ b/postgres-js/bits.js @@ -143,12 +143,3 @@ }()); - - -exports.parseDate = function (str) { - var parts = str.split(' '); - var date = parts[0].split('-'); - var time = parts[1].split(':'); - - var timestamp = date. -} diff --git a/postgres-js/parsers.js b/postgres-js/parsers.js new file mode 100644 index 0000000..e1833e3 --- /dev/null +++ b/postgres-js/parsers.js @@ -0,0 +1,77 @@ +process.mixin(GLOBAL, require("sys")); +var oids = require('./type-oids'); + +// DATE parsing + +// parse a string like YYYY-MM-DD into +var date_regex = { + "ISO": /(\d{4})-(\d{2})-(\d{2}).*/, + "ISOr": "$2/$3/$1" +} +var time_regex = { + "ISO": /.*(\d{2}):(\d{2}):(\d{2}).*/, + "ISOr": "$1:$2:$3" +} +var tz_regex = { + "ISO": /.*:\d{2}-(\d{2})$/, + "ISOr": "GMT-$100" +} + +exports.parseDateFromPostgres = function (str,datestyle,OID) { + debug("parsing string: "+ str + " with datestyle: " + datestyle + " with OID: " + OID); + + var style = datestyle.split(','); + var order = style[1]; style = style[0].replace(/^\s+|\s+$/,''); + + if (!(style in date_regex)) { + sys.debug("Error datestyle not implemented: " + style); + } + + var date='',time='',tz=''; + switch(OID) { + case oids.TIMESTAMPTZ: + tz = str.replace(tz_regex[style],tz_regex[style+'r']); + debug("Timezone: " + tz); + if (tz == str) tz = ''; + case oids.TIMESTAMP: + case oids.DATE: + date = str.replace(date_regex[style],date_regex[style+'r']); + debug("date: " + date); + if (date == str) date = ''; + if (OID==oids.DATE) break; + case oids.TIME: + time = ' ' + str.replace(time_regex[style],time_regex[style+'r']); + debug("time: " + time); + if (time == str) time = ''; + } + + date = ((date=='')?'January 1, 1970':date) + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz; + + debug("created date: " + date); + + var d = new Date(); + d.setTime(Date.parse(date)); + return d; +}; + +// turn a number 5 into a string 05 +function pad (p,num) { + num = ''+num; + return ((num.length==1)?p:'') + num; +} + +exports.formatDateForPostgres = function(d, OID) { + var date='',time='',tz=''; + switch(OID) { + case oids.TIMESTAMPTZ: + tz = '-' + d.getTimezoneOffset()/60; + case oids.TIMESTAMP: + case oids.DATE: + date = [d.getFullYear(),pad('0',d.getMonth()),pad('0',d.getDate())].join(''); + if (OID==oids.DATE) break; + case oids.TIME: + time = [pad('0',d.getHours()),pad('0',d.getMinutes()),pad('0',d.getSeconds())].join(':'); + } + + return date + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz; +} diff --git a/postgres-js/type-oids.js b/postgres-js/type-oids.js index cf46eb7..390031e 100644 --- a/postgres-js/type-oids.js +++ b/postgres-js/type-oids.js @@ -1,67 +1,67 @@ -// taken from ry's node_postgres module - -exports.BOOL = 16 -exports.BYTEA = 17 -exports.CHAR = 18 -exports.NAME = 19 -exports.INT8 = 20 -exports.INT2 = 21 -exports.INT2VECTOR = 22 -exports.INT4 = 23 -exports.REGPROC = 24 -exports.TEXT = 25 -exports.OID = 26 -exports.TID = 27 -exports.XID = 28 -exports.CID = 29 -exports.VECTOROID = 30 -exports.PG_TYPE_RELTYPE_ = 71 -exports.PG_ATTRIBUTE_RELTYPE_ = 75 -exports.PG_PROC_RELTYPE_ = 81 -exports.PG_CLASS_RELTYPE_ = 83 -exports.POINT = 600 -exports.LSEG = 601 -exports.PATH = 602 -exports.BOX = 603 -exports.POLYGON = 604 -exports.LINE = 628 -exports.FLOAT4 = 700 -exports.FLOAT8 = 701 -exports.ABSTIME = 702 -exports.RELTIME = 703 -exports.TINTERVAL = 704 -exports.UNKNOWN = 705 -exports.CIRCLE = 718 -exports.CASH = 790 -exports.MACADDR = 829 -exports.INET = 869 -exports.CIDR = 650 -exports.INT4ARRAY = 1007 -exports.ACLITEM = 1033 -exports.BPCHAR = 1042 -exports.VARCHAR = 1043 -exports.DATE = 1082 -exports.TIME = 1083 -exports.TIMESTAMP = 1114 -exports.TIMESTAMPTZ = 1184 -exports.INTERVAL = 1186 -exports.TIMETZ = 1266 -exports.BIT = 1560 -exports.VARBIT = 1562 -exports.NUMERIC = 1700 -exports.REFCURSOR = 1790 -exports.REGPROCEDURE = 2202 -exports.REGOPER = 2203 -exports.REGOPERATOR = 2204 -exports.REGCLASS = 2205 -exports.REGTYPE = 2206 -exports.RECORD = 2249 -exports.CSTRING = 2275 -exports.ANY = 2276 -exports.ANYARRAY = 2277 -exports.VOID = 2278 -exports.TRIGGER = 2279 -exports.LANGUAGE_HANDLER = 2280 -exports.INTERNAL = 2281 -exports.OPAQUE = 2282 -exports.ANYELEMENT = 2283 +// taken from ry's node_postgres module; +; +exports.BOOL = 16; +exports.BYTEA = 17; +exports.CHAR = 18; +exports.NAME = 19; +exports.INT8 = 20; +exports.INT2 = 21; +exports.INT2VECTOR = 22; +exports.INT4 = 23; +exports.REGPROC = 24; +exports.TEXT = 25; +exports.OID = 26; +exports.TID = 27; +exports.XID = 28; +exports.CID = 29; +exports.VECTOROID = 30; +exports.PG_TYPE_RELTYPE_ = 71; +exports.PG_ATTRIBUTE_RELTYPE_ = 75; +exports.PG_PROC_RELTYPE_ = 81; +exports.PG_CLASS_RELTYPE_ = 83; +exports.POINT = 600; +exports.LSEG = 601; +exports.PATH = 602; +exports.BOX = 603; +exports.POLYGON = 604; +exports.LINE = 628; +exports.FLOAT4 = 700; +exports.FLOAT8 = 701; +exports.ABSTIME = 702; +exports.RELTIME = 703; +exports.TINTERVAL = 704; +exports.UNKNOWN = 705; +exports.CIRCLE = 718; +exports.CASH = 790; +exports.MACADDR = 829; +exports.INET = 869; +exports.CIDR = 650; +exports.INT4ARRAY = 1007; +exports.ACLITEM = 1033; +exports.BPCHAR = 1042; +exports.VARCHAR = 1043; +exports.DATE = 1082; +exports.TIME = 1083; +exports.TIMESTAMP = 1114; +exports.TIMESTAMPTZ = 1184; +exports.INTERVAL = 1186; +exports.TIMETZ = 1266; +exports.BIT = 1560; +exports.VARBIT = 1562; +exports.NUMERIC = 1700; +exports.REFCURSOR = 1790; +exports.REGPROCEDURE = 2202; +exports.REGOPER = 2203; +exports.REGOPERATOR = 2204; +exports.REGCLASS = 2205; +exports.REGTYPE = 2206; +exports.RECORD = 2249; +exports.CSTRING = 2275; +exports.ANY = 2276; +exports.ANYARRAY = 2277; +exports.VOID = 2278; +exports.TRIGGER = 2279; +exports.LANGUAGE_HANDLER = 2280; +exports.INTERNAL = 2281; +exports.OPAQUE = 2282; +exports.ANYELEMENT = 2283; diff --git a/postgres.js b/postgres.js index 65db580..e57736a 100644 --- a/postgres.js +++ b/postgres.js @@ -1,14 +1,17 @@ /*jslint bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true, plusplus: true, regexp: true, undef: true, white: true, indent: 2 */ /*globals include md5 node exports */ - process.mixin(require('./postgres-js/md5')); + var bits = require('./postgres-js/bits'); +var oid = require("./postgres-js/type-oids"); +var parsers = require("./postgres-js/parsers"); var tcp = require("tcp"); var sys = require("sys"); -var oid = require("./postgres-js/type-oids.js"); exports.DEBUG = 0; +var postgres_parameters = {}; + // http://www.postgresql.org/docs/8.3/static/protocol-message-formats.html var formatter = { CopyData: function () { @@ -246,6 +249,9 @@ exports.Connection = function (database, username, password, port, host) { connection.close(); } }); + events.addListener('ParameterStatus', function(key, value) { + postgres_parameters[key] = value; + }); events.addListener('ReadyForQuery', function () { if (query_queue.length > 0) { var query = query_queue.shift(); @@ -283,8 +289,16 @@ exports.Connection = function (database, username, password, port, host) { case oid.INT4: value = parseInt(value, 10); break; - case oid.DATE: // date - value = parseDate(value); + case oid.DATE: + case oid.TIME: + case oid.TIMESTAMP: + case oid.TIMESTAMPTZ: + value = parsers.parseDateFromPostgres( + value, + postgres_parameters['DateStyle'], + description.type_id + ); + break; } } row[description.field] = value; From f8a1e8e53e2d48bf66b965ab4783359b56794f25 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 2 Dec 2009 20:32:32 -0800 Subject: [PATCH 05/13] Changed name of libs folder. The double "postgres-js" was too annoying --- {postgres-js => lib}/bits.js | 0 {postgres-js => lib}/md5.js | 0 {postgres-js => lib}/parsers.js | 0 {postgres-js => lib}/type-oids.js | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {postgres-js => lib}/bits.js (100%) rename {postgres-js => lib}/md5.js (100%) rename {postgres-js => lib}/parsers.js (100%) rename {postgres-js => lib}/type-oids.js (100%) diff --git a/postgres-js/bits.js b/lib/bits.js similarity index 100% rename from postgres-js/bits.js rename to lib/bits.js diff --git a/postgres-js/md5.js b/lib/md5.js similarity index 100% rename from postgres-js/md5.js rename to lib/md5.js diff --git a/postgres-js/parsers.js b/lib/parsers.js similarity index 100% rename from postgres-js/parsers.js rename to lib/parsers.js diff --git a/postgres-js/type-oids.js b/lib/type-oids.js similarity index 100% rename from postgres-js/type-oids.js rename to lib/type-oids.js From 8e6f507976c009cf30ee009c3998e36b92b7f8a8 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 2 Dec 2009 20:33:31 -0800 Subject: [PATCH 06/13] Fixed error caused by last rename --- postgres.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/postgres.js b/postgres.js index e57736a..3e21bc9 100644 --- a/postgres.js +++ b/postgres.js @@ -1,10 +1,10 @@ /*jslint bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true, plusplus: true, regexp: true, undef: true, white: true, indent: 2 */ /*globals include md5 node exports */ -process.mixin(require('./postgres-js/md5')); +process.mixin(require('./lib/md5')); -var bits = require('./postgres-js/bits'); -var oid = require("./postgres-js/type-oids"); -var parsers = require("./postgres-js/parsers"); +var bits = require('./lib/bits'); +var oid = require("./lib/type-oids"); +var parsers = require("./lib/parsers"); var tcp = require("tcp"); var sys = require("sys"); From a076b6cc9466fc30cefa535e3bc9cdfef4b2765b Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 2 Dec 2009 21:59:09 -0800 Subject: [PATCH 07/13] More changes --- .gitignore | 2 +- demo.js | 5 +++-- postgres.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d2554e7..9ba7be7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .bzr .bzrignore -test.js \ No newline at end of file +test.js diff --git a/demo.js b/demo.js index efb9536..24fdaf9 100644 --- a/demo.js +++ b/demo.js @@ -1,8 +1,9 @@ var sys = require("sys"); var Postgres = require("./postgres"); -var db = new Postgres.Connection("database", "username", "password"); -db.query("SELECT * FROM sometable", function (data) { +var db = new Postgres.Connection("dbname", "username", "password"); +db.query("SELECT * FROM test"); +db.query("SELECT * FROM test", function (data) { sys.p(data); }); db.close(); diff --git a/postgres.js b/postgres.js index 3e21bc9..e91ab7c 100644 --- a/postgres.js +++ b/postgres.js @@ -255,7 +255,7 @@ exports.Connection = function (database, username, password, port, host) { events.addListener('ReadyForQuery', function () { if (query_queue.length > 0) { var query = query_queue.shift(); - query_callback = query.callback; + query_callback = query.callback || function() {}; sendMessage('Query', [query.sql]); readyState = false; } else { From d509dc5004418bf34dc7540a8b7f4a77fcd1a937 Mon Sep 17 00:00:00 2001 From: Aurynn Shaw Date: Wed, 9 Dec 2009 08:42:40 -0800 Subject: [PATCH 08/13] Adds parameterized queries and prepared query support to the library. As we're not using libpq, this is achieved by using dollarquoting with randomized container names to de-taint input. This is an initial attempt - there are likely bugs and oddities I haven't discovered yet. --- README.markdown | 31 +++++++++++++ postgres.js | 120 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 9 deletions(-) diff --git a/README.markdown b/README.markdown index a404f29..21e4a99 100644 --- a/README.markdown +++ b/README.markdown @@ -3,6 +3,9 @@ This library is a implementation of the PostgreSQL backend/frontend protocol in javascript. It uses the node.js tcp and event libraries. A javascript md5 library is included for servers that require md5 password hashing (this is default). +This library also allows for the handling of prepared queries and parameterized queries. +As this library does not use libpq (which supports automatic parameterization), I use Postgres' dollar-quoting system to provide argument wrapping, before sending the query to the database. + ## Example use var sys = require("sys"); @@ -17,3 +20,31 @@ It uses the node.js tcp and event libraries. A javascript md5 library is includ } +## Example use of Parameterized Queries + + var sys = require("sys"); + var pg = require("postgres.js"); + + var db = new pg.Connection("database", "username", "password"); + db.query("SELECT * FROM yourtable WHERE id = ?", [1], function (data) { + + sys.p(data); + }); + db.close(); + +## Example use of Prepared Queries + + var sys = require("sys"); + var pg = require("postgres.js"); + + var db = new pg.Connection("database", "username", "password"); + + db.prepare("SELECT * FROM yourtable WHERE id = ?").addCallback( function (query) { + + sys.p(query); + query.execute(["1"], function (d) { + sys.p(d); + }); + /* More queries here. */ + }); + db.close(); \ No newline at end of file diff --git a/postgres.js b/postgres.js index f6dfca6..9fdaf35 100644 --- a/postgres.js +++ b/postgres.js @@ -167,15 +167,20 @@ function parse_response(code, stream) { } -exports.Connection = function (database, username, password, port) { +exports.Connection = function (database, username, password, host, port) { var connection, events, query_queue, row_description, query_callback, results, readyState, closeState; // Default to port 5432 if (port === undefined) { port = 5432; } + + t_host = host; + if (t_host === undefined) { + t_host = "localhost"; + } - connection = tcp.createConnection(port); + connection = tcp.createConnection(port, host=t_host); events = new process.EventEmitter(); query_queue = []; readyState = false; @@ -255,7 +260,7 @@ exports.Connection = function (database, username, password, port) { if (closeState) { connection.close(); } else { - readyState = true; + readyState = true; } } }); @@ -292,15 +297,112 @@ exports.Connection = function (database, username, password, port) { query_callback.call(this, results); }); - this.query = function (sql, callback) { - query_queue.push({sql: sql, callback: callback}); - if (readyState) { - events.emit('ReadyForQuery'); - } + this.query = function (sql, args, callback) { + + if (callback == null) { + + // This has no parameters to manipulate. + + // Assume the args value is the callback + sys.puts("Null callback"); + sys.p(args); + query_queue.push({sql: sql, callback: args || function () {} }); + if (readyState) { + events.emit('ReadyForQuery'); + } + } + else { + // We have an args list. + // This means, we have to map our ?'s and test for a variety of + // edge cases. + sys.puts("Got args."); + var i = 0; + var slice = md5(md5(sql)); + //sys.p(slice); + var offset = Math.floor(Math.random() * 10); + cont = "$" + slice.replace(/\d/g, "").slice(offset,4+offset) + "$"; + var treated = sql; + sys.p(cont); + if (sql.match(/\?/)) { + treated = sql.replace(/\?/g, function (str, offset, s) { + if (!args[i]) { + // raise an error + throw new Error("Argument "+i+" does not exist!"); + } + return cont+args[i]+cont; + } ); + } + sys.p(treated); + query_queue.push({sql: treated, callback: callback}); + if (readyState) { + events.emit('ReadyForQuery'); + } + } + }; + + this.prepare = function (query) { + + var r = new process.Promise(); + var name = md5(md5(query)); + var offset = Math.floor(Math.random() * 10); + name = name.replace(/\d/g, "").slice(offset,4+offset); + + var treated = query; + var i = 0; + if (query.match(/\?/)) { + + treated = treated.replace(/\?/g, function (str, p1, offset, s) { + i = i + 1; + return "$"+i; + }); + } + + stmt = "PREPARE " + name + " AS " + treated; + + var conn = this; + + query_queue.push({sql: stmt, callback: function (c) { + // R is going to be null or otherwise weird, as we're emitting + // a PREPARE, instead of anything that would return useful data + // from the database. + var q = new Stmt(name, i, conn ); + r.emitSuccess(q); + } + }); + return r; + }; + this.close = function () { closeState = true; }; + }; - +function Stmt (name, len, conn) { + var stmt = "EXECUTE "+name+" ( "; + var que = []; + for (var i = 1; i<=len; i++) { + que.push("?"); + } + stmt = stmt + que.join(",") + " )"; + + sys.puts(stmt); + + this.execute = function (args, callback) { + if (args.length > len) { + throw new Error("Cannot execute: Too many arguments"); + } + else if (args.length < len) { + // Pad out the length with nulls. + for (var i = args.length; i<= len; i++) { + args.push(null); + } + } + else { + // Nothing to see here. + ; + } + return conn.query(stmt, args, callback); + }; +} \ No newline at end of file From d4e4ef38b70e30296a5a94006f6728ea7c43cc7e Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 9 Dec 2009 14:24:44 -0800 Subject: [PATCH 09/13] continuing the merge --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index cd907a3..84ad517 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ This library is a implementation of the PostgreSQL backend/frontend protocol in javascript. It uses the node.js tcp and event libraries. A javascript md5 library is included for servers that require md5 password hashing (this is default). +This library also allows for the handling of prepared queries and parameterized queries. + ## Example use var sys = require("sys"); @@ -14,3 +16,31 @@ It uses the node.js tcp and event libraries. A javascript md5 library is includ }); db.close(); +## Example use of Parameterized Queries + + var sys = require("sys"); + var pg = require("postgres.js"); + + var db = new pg.Connection("database", "username", "password"); + db.query("SELECT * FROM yourtable WHERE id = ?", [1], function (data) { + + sys.p(data); + }); + db.close(); + +## Example use of Prepared Queries + + var sys = require("sys"); + var pg = require("postgres.js"); + + var db = new pg.Connection("database", "username", "password"); + + db.prepare("SELECT * FROM yourtable WHERE id = ?").addCallback( function (query) { + + sys.p(query); + query.execute(["1"], function (d) { + sys.p(d); + }); + /* More queries here. */ + }); + db.close(); \ No newline at end of file From 8b7541db3503d31892b8b556b12b77bca1f7699b Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Wed, 9 Dec 2009 14:31:25 -0800 Subject: [PATCH 10/13] Finish the merge --- README.markdown | 50 ------------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 README.markdown diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 21e4a99..0000000 --- a/README.markdown +++ /dev/null @@ -1,50 +0,0 @@ -# PostgreSQL for Javascript - -This library is a implementation of the PostgreSQL backend/frontend protocol in javascript. -It uses the node.js tcp and event libraries. A javascript md5 library is included for servers that require md5 password hashing (this is default). - -This library also allows for the handling of prepared queries and parameterized queries. -As this library does not use libpq (which supports automatic parameterization), I use Postgres' dollar-quoting system to provide argument wrapping, before sending the query to the database. - -## Example use - - var sys = require("sys"); - var Postgres = require('postgres.js'); - - function onLoad() { - var db = new Postgres.Connection("database", "username", "password"); - db.query("SELECT * FROM sometable", function (data) { - sys.p(data); - }); - db.close(); - } - - -## Example use of Parameterized Queries - - var sys = require("sys"); - var pg = require("postgres.js"); - - var db = new pg.Connection("database", "username", "password"); - db.query("SELECT * FROM yourtable WHERE id = ?", [1], function (data) { - - sys.p(data); - }); - db.close(); - -## Example use of Prepared Queries - - var sys = require("sys"); - var pg = require("postgres.js"); - - var db = new pg.Connection("database", "username", "password"); - - db.prepare("SELECT * FROM yourtable WHERE id = ?").addCallback( function (query) { - - sys.p(query); - query.execute(["1"], function (d) { - sys.p(d); - }); - /* More queries here. */ - }); - db.close(); \ No newline at end of file From 6db8e53d2f10257f22a5de7737fe285199ff11b2 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Thu, 10 Dec 2009 21:13:04 -0800 Subject: [PATCH 11/13] Migrated code to use node promises rather than function callbacks --- README.md | 22 ++++-- demo.js | 6 +- lib/parsers.js | 7 -- postgres.js | 190 +++++++++++++++++++++++++------------------------ 4 files changed, 116 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 84ad517..56bfe4a 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,20 @@ This library also allows for the handling of prepared queries and parameterized var sys = require("sys"); var Postgres = require("./postgres"); + Postgres.DEBUG= 1; + var db = new Postgres.Connection("database", "username", "password"); - db.query("SELECT * FROM sometable", function (data) { - sys.p(data); + + var promise = db.query("SELECT * FROM test"); + + promise.addCallback(function (data) { + sys.p(data); }); + + promise.addErrback(function (error_message) { + sys.debug(error_message); + }); + db.close(); ## Example use of Parameterized Queries @@ -22,7 +32,7 @@ This library also allows for the handling of prepared queries and parameterized var pg = require("postgres.js"); var db = new pg.Connection("database", "username", "password"); - db.query("SELECT * FROM yourtable WHERE id = ?", [1], function (data) { + db.query("SELECT * FROM yourtable WHERE id = ?", [1]).addCallback(function (data) { sys.p(data); }); @@ -35,12 +45,12 @@ This library also allows for the handling of prepared queries and parameterized var db = new pg.Connection("database", "username", "password"); - db.prepare("SELECT * FROM yourtable WHERE id = ?").addCallback( function (query) { + db.prepare("SELECT * FROM yourtable WHERE id = ?").addCallback(function (query) { sys.p(query); - query.execute(["1"], function (d) { + query.execute(["1"]).addCallback(function (d) { sys.p(d); }); /* More queries here. */ }); - db.close(); \ No newline at end of file + db.close(); diff --git a/demo.js b/demo.js index 24fdaf9..e1a604a 100644 --- a/demo.js +++ b/demo.js @@ -1,9 +1,11 @@ var sys = require("sys"); var Postgres = require("./postgres"); -var db = new Postgres.Connection("dbname", "username", "password"); +Postgres.DEBUG= 1; + +var db = new Postgres.Connection("database", "username", "password"); db.query("SELECT * FROM test"); -db.query("SELECT * FROM test", function (data) { +db.query("SELECT * FROM test").addCallback(function (data) { sys.p(data); }); db.close(); diff --git a/lib/parsers.js b/lib/parsers.js index e1833e3..f9fb143 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -18,8 +18,6 @@ var tz_regex = { } exports.parseDateFromPostgres = function (str,datestyle,OID) { - debug("parsing string: "+ str + " with datestyle: " + datestyle + " with OID: " + OID); - var style = datestyle.split(','); var order = style[1]; style = style[0].replace(/^\s+|\s+$/,''); @@ -31,24 +29,19 @@ exports.parseDateFromPostgres = function (str,datestyle,OID) { switch(OID) { case oids.TIMESTAMPTZ: tz = str.replace(tz_regex[style],tz_regex[style+'r']); - debug("Timezone: " + tz); if (tz == str) tz = ''; case oids.TIMESTAMP: case oids.DATE: date = str.replace(date_regex[style],date_regex[style+'r']); - debug("date: " + date); if (date == str) date = ''; if (OID==oids.DATE) break; case oids.TIME: time = ' ' + str.replace(time_regex[style],time_regex[style+'r']); - debug("time: " + time); if (time == str) time = ''; } date = ((date=='')?'January 1, 1970':date) + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz; - debug("created date: " + date); - var d = new Date(); d.setTime(Date.parse(date)); return d; diff --git a/postgres.js b/postgres.js index 02cb428..6d7cae0 100644 --- a/postgres.js +++ b/postgres.js @@ -172,8 +172,9 @@ function parse_response(code, stream) { exports.Connection = function (database, username, password, port, host) { - var connection, events, query_queue, row_description, query_callback, results, readyState, closeState; - + var connection, events, query_queue, row_description, results, readyState, closeState; + var query_callback, query_promise; + // Default to port 5432 if (port === undefined) { port = 5432; @@ -252,6 +253,7 @@ exports.Connection = function (database, username, password, port, host) { if (e.S === 'FATAL') { sys.debug(e.S + ": " + e.M); connection.close(); + if(query_promise) query_promise.emitError(e.M); } }); events.addListener('ParameterStatus', function(key, value) { @@ -260,7 +262,8 @@ exports.Connection = function (database, username, password, port, host) { events.addListener('ReadyForQuery', function () { if (query_queue.length > 0) { var query = query_queue.shift(); - query_callback = query.callback || function() {}; + query_callback = query.callback || null; + query_promise = query.promise || null; sendMessage('Query', [query.sql]); readyState = false; } else { @@ -311,114 +314,113 @@ exports.Connection = function (database, username, password, port, host) { results.push(row); }); events.addListener('CommandComplete', function (data) { - query_callback.call(this, results); + if(query_callback) query_callback(results); + else if(query_promise) query_promise.emitSuccess(results); }); - this.query = function (sql, args, callback) { + this.query = function (sql, args) { + var promise = new process.Promise(); + + if (args == null) { - if (callback == null) { - - // This has no parameters to manipulate. - - // Assume the args value is the callback - sys.puts("Null callback"); - sys.p(args); - query_queue.push({sql: sql, callback: args || function () {} }); - if (readyState) { - events.emit('ReadyForQuery'); - } + // This has no parameters to manipulate. + + query_queue.push({sql: sql, promise: promise}); + if (readyState) { + events.emit('ReadyForQuery'); } - else { - // We have an args list. - // This means, we have to map our ?'s and test for a variety of - // edge cases. - sys.puts("Got args."); - var i = 0; - var slice = md5(md5(sql)); - //sys.p(slice); - var offset = Math.floor(Math.random() * 10); - cont = "$" + slice.replace(/\d/g, "").slice(offset,4+offset) + "$"; - var treated = sql; - sys.p(cont); - if (sql.match(/\?/)) { - treated = sql.replace(/\?/g, function (str, offset, s) { - if (!args[i]) { - // raise an error - throw new Error("Argument "+i+" does not exist!"); - } - return cont+args[i]+cont; - } ); - } - sys.p(treated); - query_queue.push({sql: treated, callback: callback}); - if (readyState) { - events.emit('ReadyForQuery'); + } + else { + // We have an args list. + // This means, we have to map our ?'s and test for a variety of + // edge cases. + sys.puts("Got args."); + var i = 0; + var slice = md5(md5(sql)); + //sys.p(slice); + var offset = Math.floor(Math.random() * 10); + cont = "$" + slice.replace(/\d/g, "").slice(offset,4+offset) + "$"; + var treated = sql; + sys.p(cont); + if (sql.match(/\?/)) { + treated = sql.replace(/\?/g, function (str, offset, s) { + if (!args[i]) { + // raise an error + throw new Error("Argument "+i+" does not exist!"); } + return cont+args[i]+cont; + } ); } - + sys.p(treated); + query_queue.push({sql: treated, promise: promise}); + if (readyState) { + events.emit('ReadyForQuery'); + } + } + + return promise; }; this.prepare = function (query) { + + var r = new process.Promise(); + var name = md5(md5(query)); + var offset = Math.floor(Math.random() * 10); + name = name.replace(/\d/g, "").slice(offset,4+offset); + + var treated = query; + var i = 0; + if (query.match(/\?/)) { - var r = new process.Promise(); - var name = md5(md5(query)); - var offset = Math.floor(Math.random() * 10); - name = name.replace(/\d/g, "").slice(offset,4+offset); - - var treated = query; - var i = 0; - if (query.match(/\?/)) { - - treated = treated.replace(/\?/g, function (str, p1, offset, s) { - i = i + 1; - return "$"+i; - }); - } - - stmt = "PREPARE " + name + " AS " + treated; - - var conn = this; - - query_queue.push({sql: stmt, callback: function (c) { - // R is going to be null or otherwise weird, as we're emitting - // a PREPARE, instead of anything that would return useful data - // from the database. - var q = new Stmt(name, i, conn ); - r.emitSuccess(q); - } + treated = treated.replace(/\?/g, function (str, p1, offset, s) { + i = i + 1; + return "$"+i; }); - return r; + } + + stmt = "PREPARE " + name + " AS " + treated; + + var conn = this; + + query_queue.push({sql: stmt, callback: function (c) { + var q = new Stmt(name, i, conn ); + r.emitSuccess(q); + } + }); + return r; }; this.close = function () { - closeState = true; + closeState = true; }; }; function Stmt (name, len, conn) { - var stmt = "EXECUTE "+name+" ( "; - var que = []; - for (var i = 1; i<=len; i++) { - que.push("?"); + var stmt = "EXECUTE "+name+" ( "; + var que = []; + for (var i = 1; i<=len; i++) { + que.push("?"); + } + stmt = stmt + que.join(",") + " )"; + + sys.puts(stmt); + + this.execute = function (args) { + if (args.length > len) { + throw new Error("Cannot execute: Too many arguments"); } - stmt = stmt + que.join(",") + " )"; - - sys.puts(stmt); - - this.execute = function (args, callback) { - if (args.length > len) { - throw new Error("Cannot execute: Too many arguments"); - } - else if (args.length < len) { - // Pad out the length with nulls. - for (var i = args.length; i<= len; i++) { - args.push(null); - } - } - else { - // Nothing to see here. - ; - } - return conn.query(stmt, args, callback); - }; + else if (args.length < len) { + // Pad out the length with nulls. + for (var i = args.length; i<= len; i++) { + args.push(null); + } + } + else { + // Nothing to see here. + ; + } + + return conn.query(stmt, args); + }; +} From 233b70d2e5d3564dc8664e47b87b1d751c923ec5 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Thu, 17 Dec 2009 00:02:05 -0800 Subject: [PATCH 12/13] Fixed minor date bug --- lib/parsers.js | 10 +++++++--- postgres.js | 30 +++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index f9fb143..b394009 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -1,5 +1,5 @@ -process.mixin(GLOBAL, require("sys")); -var oids = require('./type-oids'); +var oids = require('./type-oids'), + sys = require('sys'); // DATE parsing @@ -44,6 +44,7 @@ exports.parseDateFromPostgres = function (str,datestyle,OID) { var d = new Date(); d.setTime(Date.parse(date)); + return d; }; @@ -54,17 +55,20 @@ function pad (p,num) { } exports.formatDateForPostgres = function(d, OID) { + error("formatting for postgres: " +d); var date='',time='',tz=''; switch(OID) { case oids.TIMESTAMPTZ: tz = '-' + d.getTimezoneOffset()/60; case oids.TIMESTAMP: case oids.DATE: - date = [d.getFullYear(),pad('0',d.getMonth()),pad('0',d.getDate())].join(''); + date = [d.getFullYear(),pad('0',d.getMonth()+1),pad('0',d.getDate())].join(''); if (OID==oids.DATE) break; case oids.TIME: time = [pad('0',d.getHours()),pad('0',d.getMinutes()),pad('0',d.getSeconds())].join(':'); } + + error("formatted: "+(date + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz)); return date + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz; } diff --git a/postgres.js b/postgres.js index 6d7cae0..9efa8f3 100644 --- a/postgres.js +++ b/postgres.js @@ -190,6 +190,11 @@ exports.Connection = function (database, username, password, port, host) { query_queue = []; readyState = false; closeState = false; + timedout = false; + + function reconnect() { + connection.connect(port, host=t_host); + } // Sends a message to the postgres server function sendMessage(type, args) { @@ -235,11 +240,18 @@ exports.Connection = function (database, username, password, port, host) { connection.addListener("eof", function (data) { connection.close(); }); - connection.addListener("disconnect", function (had_error) { + connection.addListener("close", function (had_error) { if (had_error) { sys.debug("CONNECTION DIED WITH ERROR"); + } else if (timedout) { + reconnect(); + timedout = false; } }); + connection.addListener("timeout", function () { + info("server timed out... reconnecting"); + timedout = true; + }); // Set up callbacks to automatically do the login events.addListener('AuthenticationMD5Password', function (salt) { @@ -253,13 +265,17 @@ exports.Connection = function (database, username, password, port, host) { if (e.S === 'FATAL') { sys.debug(e.S + ": " + e.M); connection.close(); - if(query_promise) query_promise.emitError(e.M); } + if(query_promise != null) query_promise.emitError(e.M); }); events.addListener('ParameterStatus', function(key, value) { postgres_parameters[key] = value; }); events.addListener('ReadyForQuery', function () { + if(connection.readyState == "closed") { + reconnect(); + return; + } if (query_queue.length > 0) { var query = query_queue.shift(); query_callback = query.callback || null; @@ -334,24 +350,24 @@ exports.Connection = function (database, username, password, port, host) { // We have an args list. // This means, we have to map our ?'s and test for a variety of // edge cases. - sys.puts("Got args."); + if(exports.DEBUG > 0) sys.puts("Got args."); var i = 0; var slice = md5(md5(sql)); //sys.p(slice); var offset = Math.floor(Math.random() * 10); cont = "$" + slice.replace(/\d/g, "").slice(offset,4+offset) + "$"; var treated = sql; - sys.p(cont); + if(exports.DEBUG > 0) sys.p(cont); if (sql.match(/\?/)) { treated = sql.replace(/\?/g, function (str, offset, s) { if (!args[i]) { // raise an error throw new Error("Argument "+i+" does not exist!"); } - return cont+args[i]+cont; + return cont+args[i++]+cont; } ); } - sys.p(treated); + if(exports.DEBUG > 0) sys.p(treated); query_queue.push({sql: treated, promise: promise}); if (readyState) { events.emit('ReadyForQuery'); @@ -404,7 +420,7 @@ function Stmt (name, len, conn) { } stmt = stmt + que.join(",") + " )"; - sys.puts(stmt); + if(exports.DEBUG > 0) sys.puts(stmt); this.execute = function (args) { if (args.length > len) { From 517ad28f266604f85dbaa5551817283daae90f92 Mon Sep 17 00:00:00 2001 From: Carl Sverre Date: Thu, 17 Dec 2009 00:03:32 -0800 Subject: [PATCH 13/13] Forgot to remove debugging cmds --- lib/parsers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/parsers.js b/lib/parsers.js index b394009..ef029bf 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -55,7 +55,6 @@ function pad (p,num) { } exports.formatDateForPostgres = function(d, OID) { - error("formatting for postgres: " +d); var date='',time='',tz=''; switch(OID) { case oids.TIMESTAMPTZ: @@ -68,7 +67,5 @@ exports.formatDateForPostgres = function(d, OID) { time = [pad('0',d.getHours()),pad('0',d.getMinutes()),pad('0',d.getSeconds())].join(':'); } - error("formatted: "+(date + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz)); - return date + ((time=='')?'':' ') + time + ((tz=='')?'':' ') + tz; }