"postgres" module needs work, author gone, patch included

J

Jeff Davis

--=-cap+U2nqJnIHXi7qCHyV
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

The "postgres" module is old and it's been a long time since there's
been a release. The module has the following problems:

* uses PQescapeString() and PQescapeBytea(), both of which are long-
since deprecated, and provides no interface to the newer functions
(which have been around for a long time).

* uses wrong quoting routine to quote connection string arguments.

* release version doesn't provide any interface to PQexecParams().

* snapshot introduces some ruby array to PG array type-awareness, but
it's broken

I tried to contact the maintainer, (e-mail address removed), but got no
response. I tried to contact the previous maintainer,
(e-mail address removed), and got a bounce.

I wrote a patch that makes the following changes to the module from the
snapshot version:

* added instance methods "escape" and "escape_bytea" which use
PQescapeStringConn() and PQescapeByteaConn(). The class methods by the
same names should be deprecated, but I left them in for compatibility.

* added the instance method "unescape_bytea", which is the same as the
class method by the same name. This didn't strictly need to be done, but
I did this for consistency with "escape_bytea" which is now an instance
method.

* added an instance and class method "quote_ident" to quote identifiers.

* wrote separate quoting routine (not exported) to properly quote
connection string arguments according to the documented quoting rules
for the connection string arguments. The old code did this by using the
same routine as for quoting values passed to a SQL string, which is
wrong.

* removed code that tried to convert ruby arrays into postgresql arrays.
It was broken (in the quote method, did not actually quote the array),
and the code should be substantially refactored to better enable more
intelligent type awareness from ruby to postgresql. I could reintroduce
this if people care about more type awareness.

I'd like to do some more serious refactoring, but I just wanted to get
some feedback first. Who uses this module, and what do you think about
my changes? How old of a client library do we want to support? The
current module uses ugly #define directives to bolt on support for
ancient versions of libpq, is this necessary?

Should I fork and put a project on RAA, or should I try to take over the
current "postgres" module?

Regards,
Jeff Davis


--=-cap+U2nqJnIHXi7qCHyV
Content-Disposition: attachment; filename=ruby-postgres.diff
Content-Type: text/x-patch; name=ruby-postgres.diff; charset=UTF-8
Content-Transfer-Encoding: 7bit

*** ruby-postgres.orig/postgres.c Fri Jan 19 10:03:06 2007
--- ruby-postgres/postgres.c Fri Jan 19 17:18:52 2007
***************
*** 106,111 ****
--- 106,112 ----
}

static int build_key_value_string_i(VALUE key, VALUE value, VALUE result);
+ static PGconn *get_pgconn(VALUE obj);

static PGconn *
try_connectdb(arg)
***************
*** 198,213 ****
return Qnil;
}

- static VALUE format_array_element(VALUE obj);
-
static VALUE
pgconn_s_format(self, obj)
VALUE self;
VALUE obj;
{
- VALUE result;
- int tainted;
- long i;

switch(TYPE(obj)) {
case T_STRING:
--- 199,209 ----
***************
*** 223,241 ****
case T_NIL:
return rb_str_new2("NULL");

- case T_ARRAY:
- result = rb_str_buf_new2("{");
- tainted = OBJ_TAINTED(obj);
- for (i = 0; i < RARRAY(obj)->len; i++) {
- VALUE element = format_array_element(RARRAY(obj)->ptr);
- if (OBJ_TAINTED(RARRAY(obj)->ptr)) tainted = Qtrue;
- if (i > 0) rb_str_buf_cat2(result, ", ");
- rb_str_buf_append(result, element);
- }
- rb_str_buf_cat2(result, "}");
- if (tainted) OBJ_TAINT(result);
- return result;
-
default:
if (CLASS_OF(obj) == rb_cBigDecimal) {
return rb_funcall(obj, rb_intern("to_s"), 1, rb_str_new2("F"));
--- 219,224 ----
***************
*** 248,263 ****
}
}

static VALUE
! format_array_element(obj)
! VALUE obj;
{
if (TYPE(obj) == T_STRING) {
! obj = rb_funcall(obj, rb_intern("gsub"), 2, rb_reg_new("(?=[\\\\\"])", 9, 0), rb_str_new2("\\"));
! return rb_funcall(obj, rb_intern("gsub!"), 2, rb_reg_new("^|$", 3, 0), rb_str_new2("\""));
}
else {
! return pgconn_s_format(rb_cPGconn, obj);
}
}

--- 231,274 ----
}
}

+
+ /*
+ * call-seq:
+ * PGconn.quote( obj )
+ * PGconn.quote( obj ) { |obj| ... }
+ * PGconn.format( obj )
+ * PGconn.format( obj ) { |obj| ... }
+ *
+ * If _obj_ is a Number, String, Array, +nil+, +true+, or +false+ then
+ * #quote returns a String representation of that object safe for use in PostgreSQL.
+ *
+ * If _obj_ is not one of the above classes and a block is supplied to #quote,
+ * the block is invoked, passing along the object. The return value from the
+ * block is returned as a string.
+ *
+ * If _obj_ is not one of the recognized classes andno block is supplied,
+ * a PGError is raised.
+ */
static VALUE
! pgconn_s_quote(self, obj)
! VALUE self, obj;
{
+ char* quoted;
+ int size;
+ VALUE result;
+
if (TYPE(obj) == T_STRING) {
! /* length * 2 because every char could require escaping */
! /* + 2 for the quotes, + 1 for the null terminator */
! quoted = ALLOCA_N(char, RSTRING(obj)->len * 2 + 2 + 1);
! size = PQescapeString(quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len);
! *quoted = *(quoted + size + 1) = SINGLE_QUOTE;
! result = rb_str_new(quoted, size + 2);
! OBJ_INFECT(result, obj);
! return result;
}
else {
! return pgconn_s_format(self, obj);
}
}

***************
*** 279,296 ****
* a PGError is raised.
*/
static VALUE
! pgconn_s_quote(self, obj)
VALUE self, obj;
{
char* quoted;
! int size;
VALUE result;

if (TYPE(obj) == T_STRING) {
/* length * 2 because every char could require escaping */
/* + 2 for the quotes, + 1 for the null terminator */
quoted = ALLOCA_N(char, RSTRING(obj)->len * 2 + 2 + 1);
! size = PQescapeString(quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len);
*quoted = *(quoted + size + 1) = SINGLE_QUOTE;
result = rb_str_new(quoted, size + 2);
OBJ_INFECT(result, obj);
--- 290,307 ----
* a PGError is raised.
*/
static VALUE
! pgconn_quote(self, obj)
VALUE self, obj;
{
char* quoted;
! int size,error;
VALUE result;

if (TYPE(obj) == T_STRING) {
/* length * 2 because every char could require escaping */
/* + 2 for the quotes, + 1 for the null terminator */
quoted = ALLOCA_N(char, RSTRING(obj)->len * 2 + 2 + 1);
! size = PQescapeStringConn(get_pgconn(self),quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len, &error);
*quoted = *(quoted + size + 1) = SINGLE_QUOTE;
result = rb_str_new(quoted, size + 2);
OBJ_INFECT(result, obj);
***************
*** 301,306 ****
--- 312,342 ----
}
}

+ static VALUE
+ pgconn_s_quote_connstr(string)
+ VALUE string;
+ {
+ char *str,*ptr;
+ int i,j=0,len;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ ptr = RSTRING(string)->ptr;
+ len = RSTRING(string)->len;
+ str = ALLOCA_N(char, len * 2 + 2 + 1);
+ str[j++] = '\'';
+ for(i = 0; i < len; i++) {
+ if(ptr == '\'' || ptr == '\\')
+ str[j++] = '\\';
+ str[j++] = ptr;
+ }
+ str[j++] = '\'';
+ result = rb_str_new(str, j);
+ OBJ_INFECT(result, string);
+ return result;
+ }
+
static int
build_key_value_string_i(key, value, result)
VALUE key, value, result;
***************
*** 309,323 ****
if (key == Qundef) return ST_CONTINUE;
key_value = rb_str_dup(key);
rb_str_cat(key_value, "=", 1);
! rb_str_concat(key_value, pgconn_s_quote(rb_cPGconn, value));
rb_ary_push(result, key_value);
return ST_CONTINUE;
}

/*
* call-seq:
! * PGconn.escape( str )
*
* Returns a SQL-safe version of the String _str_. Unlike #quote, does not wrap the String in '...'.
*/
static VALUE
--- 345,390 ----
if (key == Qundef) return ST_CONTINUE;
key_value = rb_str_dup(key);
rb_str_cat(key_value, "=", 1);
! rb_str_concat(key_value, pgconn_s_quote_connstr(value));
rb_ary_push(result, key_value);
return ST_CONTINUE;
}

/*
* call-seq:
! * PGconn.quote_ident( str )
*
+ * Returns a SQL-safe identifier.
+ */
+ static VALUE
+ pgconn_s_quote_ident(self, string)
+ VALUE self;
+ VALUE string;
+ {
+ char *str,*ptr;
+ int i,j=0,len;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ ptr = RSTRING(string)->ptr;
+ len = RSTRING(string)->len;
+ str = ALLOCA_N(char, len * 2 + 2 + 1);
+ str[j++] = '"';
+ for(i = 0; i < len; i++) {
+ if(ptr == '"')
+ str[j++] = '"';
+ else if(ptr == '\0')
+ rb_raise(rb_ePGError, "Identifier cannot contain NULL bytes");
+ str[j++] = ptr;
+ }
+ str[j++] = '"';
+ result = rb_str_new(str, j);
+ OBJ_INFECT(result, string);
+ return result;
+ }
+
+ /*
* Returns a SQL-safe version of the String _str_. Unlike #quote, does not wrap the String in '...'.
*/
static VALUE
***************
*** 325,331 ****
VALUE self;
VALUE string;
{
! char* escaped;
int size;
VALUE result;

--- 392,398 ----
VALUE self;
VALUE string;
{
! char *escaped;
int size;
VALUE result;

***************
*** 339,344 ****
--- 406,432 ----
}

/*
+ * Returns a SQL-safe version of the String _str_. Unlike #quote, does not wrap the String in '...'.
+ */
+ static VALUE
+ pgconn_escape(self, string)
+ VALUE self;
+ VALUE string;
+ {
+ char *escaped;
+ int size,error;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ escaped = ALLOCA_N(char, RSTRING(string)->len * 2 + 1);
+ size = PQescapeStringConn(get_pgconn(self),escaped, RSTRING(string)->ptr, RSTRING(string)->len, &error);
+ result = rb_str_new(escaped, size);
+ OBJ_INFECT(result, string);
+ return result;
+ }
+
+ /*
* call-seq:
* PGconn.escape_bytea( obj )
*
***************
*** 378,383 ****
--- 466,509 ----

/*
* call-seq:
+ * PGconn.escape_bytea( obj )
+ *
+ * Escapes binary data for use within an SQL command with the type +bytea+.
+ *
+ * Certain byte values must be escaped (but all byte values may be escaped)
+ * when used as part of a +bytea+ literal in an SQL statement. In general, to
+ * escape a byte, it is converted into the three digit octal number equal to
+ * the octet value, and preceded by two backslashes. The single quote (') and
+ * backslash (\) characters have special alternative escape sequences.
+ * #escape_bytea performs this operation, escaping only the minimally required bytes.
+ *
+ * See the PostgreSQL documentation on PQescapeBytea[http://www.postgresql.org/docs/current/interactive/libpq-exec.html#LIBPQ-EXEC-ESCAPE-BYTEA] for more information.
+ */
+ static VALUE
+ pgconn_escape_bytea(self, obj)
+ VALUE self;
+ VALUE obj;
+ {
+ char *from, *to;
+ size_t from_len, to_len;
+ VALUE ret;
+
+ Check_Type(obj, T_STRING);
+ from = RSTRING(obj)->ptr;
+ from_len = RSTRING(obj)->len;
+
+ to = (char *)PQescapeByteaConn(get_pgconn(self),from, from_len, &to_len);
+
+ ret = rb_str_new(to, to_len - 1);
+ OBJ_INFECT(ret, obj);
+
+ PQfreemem(to);
+
+ return ret;
+ }
+
+ /*
+ * call-seq:
* PGconn.unescape_bytea( obj )
*
* Converts an escaped string representation of binary data into binary data --- the
***************
*** 600,606 ****

Check_Type(str, T_STRING);

! while (result = PQgetResult(conn)) {
PQclear(result);
}

--- 726,732 ----

Check_Type(str, T_STRING);

! while ((result = PQgetResult(conn)) != NULL) {
PQclear(result);
}

***************
*** 2517,2522 ****
--- 2643,2649 ----
rb_define_singleton_method(rb_cPGconn, "escape_bytea", pgconn_s_escape_bytea, 1);
rb_define_singleton_method(rb_cPGconn, "unescape_bytea", pgconn_s_unescape_bytea, 1);
rb_define_singleton_method(rb_cPGconn, "translate_results=", pgconn_s_translate_results_set, 1);
+ rb_define_singleton_method(rb_cPGconn, "quote_ident", pgconn_s_quote_ident, 1);

rb_define_const(rb_cPGconn, "CONNECTION_OK", INT2FIX(CONNECTION_OK));
rb_define_const(rb_cPGconn, "CONNECTION_BAD", INT2FIX(CONNECTION_BAD));
***************
*** 2551,2556 ****
--- 2678,2690 ----
rb_define_method(rb_cPGconn, "transaction_status", pgconn_transaction_status, 0);
rb_define_method(rb_cPGconn, "protocol_version", pgconn_protocol_version, 0);
rb_define_method(rb_cPGconn, "server_version", pgconn_server_version, 0);
+ rb_define_method(rb_cPGconn, "escape", pgconn_escape, 1);
+ rb_define_method(rb_cPGconn, "escape_bytea", pgconn_escape_bytea, 1);
+ rb_define_method(rb_cPGconn, "unescape_bytea", pgconn_s_unescape_bytea, 1);
+ rb_define_method(rb_cPGconn, "quote", pgconn_quote, 1);
+ rb_define_method(rb_cPGconn, "quote_ident", pgconn_s_quote_ident, 1);
+ rb_define_alias(rb_cPGconn, "format", "quote");
+
/* following line is for rdoc */
/* rb_define_method(rb_cPGconn, "lastval", pgconn_lastval, 0); */


--=-cap+U2nqJnIHXi7qCHyV--
 
M

Marc Heiler

I'd like to do some more serious refactoring, but I just wanted to get
some feedback first.

Actually I tried to use it but somehow I didnt even finished compiling.
After looking at some info in it (Readme, the website) I decided to
skip this and stick to sqlite or mysql instead, perhaps.

Nice to see I didnt happen to be the only one that tried it :)
 
D

Daniel Berger

Jeff said:
The "postgres" module is old and it's been a long time since there's
been a release. The module has the following problems:

<snip>

I talked to Dave Lee a while back and he indicated to me that a new
release was imminent. Mind you, I think that was a few months ago now,
so I'm not sure what happened. I'll try to contact him again and let
you know what happens.

Worst case scenario is I give you admin rights on the DBI project, and
we bundle the postgres driver with dbd-postgres directly.

Regards,

Dan
 
T

Tom Copeland

I'd like to do some more serious refactoring, but I just wanted to get
some feedback first. Who uses this module, and what do you think about
my changes? How old of a client library do we want to support? The
current module uses ugly #define directives to bolt on support for
ancient versions of libpq, is this necessary?

I use it with PostgreSQL 8.2 and I've been pretty happy with it.
Haven't looked at any of the code, though. Sounds like you're bringing
it up to speed, which is great!

Yours,

Tom
 
J

Jeff Davis

Marc said:
Actually I tried to use it but somehow I didnt even finished compiling.
After looking at some info in it (Readme, the website) I decided to
skip this and stick to sqlite or mysql instead, perhaps.

Nice to see I didnt happen to be the only one that tried it :)

Ouch, you avoided postgresql because the ruby driver was that bad?

Can you please send me the info in the form of a bug report? That is,
include platform, versions, exact errors, etc. If you're having a
problem, a lot of people are, and I'd like to make postgresql a
first-class supported database in Ruby.

Regards,
Jeff Davis
 
J

Jeff Davis

Daniel said:
<snip>

I talked to Dave Lee a while back and he indicated to me that a new
release was imminent. Mind you, I think that was a few months ago now,
so I'm not sure what happened. I'll try to contact him again and let
you know what happens.

Worst case scenario is I give you admin rights on the DBI project, and
we bundle the postgres driver with dbd-postgres directly.

Please let me know if you get more information from Dave Lee.

Also let me know if ruby-dbi needs anything in particular from the driver.

Regards,
Jeff Davis
 
J

Jason LaRiviere

Jeff said:
The "postgres" module is old and it's been a long time since there's
been a release. The module has the following problems:

I use postgres on openbsd and netbsd without incident, but recently got
a macbook, where it does nothing. I'm using postgres-pr there, but there
are differences - notably between #escape and #quote. It is less than
ideal to account for them.

I haven't looked into the problem too deeply (read: at all) yet, but I'd
be glad to help with any effort to improve the postgres lib.
 
J

Jeff Davis

Jason said:
I haven't looked into the problem too deeply (read: at all) yet, but I'd
be glad to help with any effort to improve the postgres lib.

What would help me the most is to do one or more of the following:

(1) Test my patch against the snapshot version available from the
'postgres' module website, see if it fixes the problem
(2) If the problem is not fixed, send a more detailed report. If it is
fixed, let me know.
(3) Let me know of any extra things you'd like the driver to do, or any
behaviors you'd like to change. In particular what do you expect from
"escape" and "quote".

I don't know anything about making the module portable to OS X, but it
doesn't sound hard to do after I have some more details.

I'll keep hacking away, and I'll set up a site and an SVN repo, and I'll
make an announcement. It sounds like there's demand. Thanks for your input.

Regards,
Jeff Davis
 
J

Jason LaRiviere

Jeff said:
What would help me the most is to do one or more of the following:

(1) Test my patch against the snapshot version available from the
'postgres' module website, see if it fixes the problem
(2) If the problem is not fixed, send a more detailed report. If it is
fixed, let me know.
(3) Let me know of any extra things you'd like the driver to do, or any
behaviors you'd like to change. In particular what do you expect from
"escape" and "quote".

I don't know anything about making the module portable to OS X, but it
doesn't sound hard to do after I have some more details.

I'll keep hacking away, and I'll set up a site and an SVN repo, and I'll
make an announcement. It sounds like there's demand. Thanks for your input.

I'm on board; a couple notes:
http://ruby.scripting.ca/postgres/ appears to be the homepage for the
project, and offers a snapshot dated 2005.12.21.

http://rubyforge.org/frs/download.php/9553 offers a snapshot dated
2006.04.06. This package forms the basis for the openbsd port, and will
be the one I will be patching against and testing. If all is well, I
will submit an updated port for openbsd including your patch.

The project homepage also specifies that `this library works with
PostgreSQL 6.4-8.1', and the macbook is running postgresql 8.2.1. This
may, or may not have something to do with it's refusal to work. :p

I will confirm this with an update to pgsql 8.2.1 on the openbsd machines
and get back to you.
 
K

Kevin Williams

I use the postgres driver everywhere - Windows, Linux, Mac, production,
dev. It seems to work from ActiveRecord, but that's about all I can tell
you. Sometimes the gem install deletes the binary after creating it, but
I just go into that directory and 'make' it again.

Long story short, yes someone uses it.
 
J

Jason LaRiviere

--3MwIy2ne0vdjdPXF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Jason said:
I'm on board; a couple notes:
http://ruby.scripting.ca/postgres/ appears to be the homepage for the
project, and offers a snapshot dated 2005.12.21.

http://rubyforge.org/frs/download.php/9553 offers a snapshot dated
2006.04.06. This package forms the basis for the openbsd port, and will
be the one I will be patching against and testing. If all is well, I
will submit an updated port for openbsd including your patch.

The project homepage also specifies that `this library works with
PostgreSQL 6.4-8.1', and the macbook is running postgresql 8.2.1. This
may, or may not have something to do with it's refusal to work. :p

I will confirm this with an update to pgsql 8.2.1 on the openbsd machines
and get back to you.

Find attatched a diff that applies cleanly to the 2006.04.06 source.
Also, all teh cool kids are using unified diffs. :p

It builds just fine. Further testing is pending...

--
GPG/PGP key ID: 0x3A410DBD | http://pgp.mit.edu
7B3F 4505 7D9A 7FDE 83C9 52C2 4909 59B9 3A41 0DBD

--3MwIy2ne0vdjdPXF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ruby-postgres.diff"

--- ruby-postgres.orig/postgres.c Thu Apr 6 12:28:10 2006
+++ ruby-postgres/postgres.c Sat Jan 20 19:57:22 2007
@@ -107,6 +107,7 @@
}

static int build_key_value_string_i(VALUE key, VALUE value, VALUE result);
+static PGconn *get_pgconn(VALUE obj);

static PGconn *
try_connectdb(arg)
@@ -199,16 +200,11 @@
return fact;
}

-static VALUE format_array_element(VALUE obj);
-
static VALUE
pgconn_s_format(self, obj)
VALUE self;
VALUE obj;
{
- VALUE result;
- int tainted;
- long i;

switch(TYPE(obj)) {
case T_STRING:
@@ -224,19 +220,6 @@
case T_NIL:
return rb_str_new2("NULL");

- case T_ARRAY:
- result = rb_str_buf_new2("{");
- tainted = OBJ_TAINTED(obj);
- for (i = 0; i < RARRAY(obj)->len; i++) {
- VALUE element = format_array_element(RARRAY(obj)->ptr);
- if (OBJ_TAINTED(RARRAY(obj)->ptr)) tainted = Qtrue;
- if (i > 0) rb_str_buf_cat2(result, ", ");
- rb_str_buf_append(result, element);
- }
- rb_str_buf_cat2(result, "}");
- if (tainted) OBJ_TAINT(result);
- return result;
-
default:
if (CLASS_OF(obj) == rb_cBigDecimal) {
return rb_funcall(obj, rb_intern("to_s"), 1, rb_str_new2("F"));
@@ -249,16 +232,44 @@
}
}

+
+/*
+ * call-seq:
+ * PGconn.quote( obj )
+ * PGconn.quote( obj ) { |obj| ... }
+ * PGconn.format( obj )
+ * PGconn.format( obj ) { |obj| ... }
+ *
+ * If _obj_ is a Number, String, Array, +nil+, +true+, or +false+ then
+ * #quote returns a String representation of that object safe for use in PostgreSQL.
+ *
+ * If _obj_ is not one of the above classes and a block is supplied to #quote,
+ * the block is invoked, passing along the object. The return value from the
+ * block is returned as a string.
+ *
+ * If _obj_ is not one of the recognized classes andno block is supplied,
+ * a PGError is raised.
+ */
static VALUE
-format_array_element(obj)
- VALUE obj;
+pgconn_s_quote(self, obj)
+ VALUE self, obj;
{
+ char* quoted;
+ int size;
+ VALUE result;
+
if (TYPE(obj) == T_STRING) {
- obj = rb_funcall(obj, rb_intern("gsub"), 2, rb_reg_new("(?=[\\\\\"])", 9, 0), rb_str_new2("\\"));
- return rb_funcall(obj, rb_intern("gsub!"), 2, rb_reg_new("^|$", 3, 0), rb_str_new2("\""));
+ /* length * 2 because every char could require escaping */
+ /* + 2 for the quotes, + 1 for the null terminator */
+ quoted = ALLOCA_N(char, RSTRING(obj)->len * 2 + 2 + 1);
+ size = PQescapeString(quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len);
+ *quoted = *(quoted + size + 1) = SINGLE_QUOTE;
+ result = rb_str_new(quoted, size + 2);
+ OBJ_INFECT(result, obj);
+ return result;
}
else {
- return pgconn_s_format(rb_cPGconn, obj);
+ return pgconn_s_format(self, obj);
}
}

@@ -280,18 +291,18 @@
* a PGError is raised.
*/
static VALUE
-pgconn_s_quote(self, obj)
+pgconn_quote(self, obj)
VALUE self, obj;
{
char* quoted;
- int size;
+ int size,error;
VALUE result;

if (TYPE(obj) == T_STRING) {
/* length * 2 because every char could require escaping */
/* + 2 for the quotes, + 1 for the null terminator */
quoted = ALLOCA_N(char, RSTRING(obj)->len * 2 + 2 + 1);
- size = PQescapeString(quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len);
+ size = PQescapeStringConn(get_pgconn(self),quoted + 1, RSTRING(obj)->ptr, RSTRING(obj)->len, &error);
*quoted = *(quoted + size + 1) = SINGLE_QUOTE;
result = rb_str_new(quoted, size + 2);
OBJ_INFECT(result, obj);
@@ -302,6 +313,31 @@
}
}

+static VALUE
+pgconn_s_quote_connstr(string)
+ VALUE string;
+{
+ char *str,*ptr;
+ int i,j=0,len;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ ptr = RSTRING(string)->ptr;
+ len = RSTRING(string)->len;
+ str = ALLOCA_N(char, len * 2 + 2 + 1);
+ str[j++] = '\'';
+ for(i = 0; i < len; i++) {
+ if(ptr == '\'' || ptr == '\\')
+ str[j++] = '\\';
+ str[j++] = ptr;
+ }
+ str[j++] = '\'';
+ result = rb_str_new(str, j);
+ OBJ_INFECT(result, string);
+ return result;
+}
+
static int
build_key_value_string_i(key, value, result)
VALUE key, value, result;
@@ -310,15 +346,46 @@
if (key == Qundef) return ST_CONTINUE;
key_value = (TYPE(key) == T_STRING ? rb_str_dup(key) : rb_obj_as_string(key));
rb_str_cat(key_value, "=", 1);
- rb_str_concat(key_value, pgconn_s_quote(rb_cPGconn, value));
+ rb_str_concat(key_value, pgconn_s_quote_connstr(value));
rb_ary_push(result, key_value);
return ST_CONTINUE;
}

/*
* call-seq:
- * PGconn.escape( str )
+ * PGconn.quote_ident( str )
*
+ * Returns a SQL-safe identifier.
+ */
+static VALUE
+pgconn_s_quote_ident(self, string)
+ VALUE self;
+ VALUE string;
+{
+ char *str,*ptr;
+ int i,j=0,len;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ ptr = RSTRING(string)->ptr;
+ len = RSTRING(string)->len;
+ str = ALLOCA_N(char, len * 2 + 2 + 1);
+ str[j++] = '"';
+ for(i = 0; i < len; i++) {
+ if(ptr == '"')
+ str[j++] = '"';
+ else if(ptr == '\0')
+ rb_raise(rb_ePGError, "Identifier cannot contain NULL bytes");
+ str[j++] = ptr;
+ }
+ str[j++] = '"';
+ result = rb_str_new(str, j);
+ OBJ_INFECT(result, string);
+ return result;
+}
+
+/*
* Returns a SQL-safe version of the String _str_. Unlike #quote, does not wrap the String in '...'.
*/
static VALUE
@@ -326,7 +393,7 @@
VALUE self;
VALUE string;
{
- char* escaped;
+ char *escaped;
int size;
VALUE result;

@@ -340,6 +407,27 @@
}

/*
+ * Returns a SQL-safe version of the String _str_. Unlike #quote, does not wrap the String in '...'.
+ */
+static VALUE
+pgconn_escape(self, string)
+ VALUE self;
+ VALUE string;
+{
+ char *escaped;
+ int size,error;
+ VALUE result;
+
+ Check_Type(string, T_STRING);
+
+ escaped = ALLOCA_N(char, RSTRING(string)->len * 2 + 1);
+ size = PQescapeStringConn(get_pgconn(self),escaped, RSTRING(string)->ptr, RSTRING(string)->len, &error);
+ result = rb_str_new(escaped, size);
+ OBJ_INFECT(result, string);
+ return result;
+}
+
+/*
* call-seq:
* PGconn.escape_bytea( obj )
*
@@ -379,6 +467,44 @@

/*
* call-seq:
+ * PGconn.escape_bytea( obj )
+ *
+ * Escapes binary data for use within an SQL command with the type +bytea+.
+ *
+ * Certain byte values must be escaped (but all byte values may be escaped)
+ * when used as part of a +bytea+ literal in an SQL statement. In general, to
+ * escape a byte, it is converted into the three digit octal number equal to
+ * the octet value, and preceded by two backslashes. The single quote (') and
+ * backslash (\) characters have special alternative escape sequences.
+ * #escape_bytea performs this operation, escaping only the minimally required bytes.
+ *
+ * See the PostgreSQL documentation on PQescapeBytea[http://www.postgresql.org/docs/current/interactive/libpq-exec.html#LIBPQ-EXEC-ESCAPE-BYTEA] for more information.
+ */
+static VALUE
+pgconn_escape_bytea(self, obj)
+ VALUE self;
+ VALUE obj;
+{
+ char *from, *to;
+ size_t from_len, to_len;
+ VALUE ret;
+
+ Check_Type(obj, T_STRING);
+ from = RSTRING(obj)->ptr;
+ from_len = RSTRING(obj)->len;
+
+ to = (char *)PQescapeByteaConn(get_pgconn(self),from, from_len, &to_len);
+
+ ret = rb_str_new(to, to_len - 1);
+ OBJ_INFECT(ret, obj);
+
+ PQfreemem(to);
+
+ return ret;
+}
+
+/*
+ * call-seq:
* PGconn.unescape_bytea( obj )
*
* Converts an escaped string representation of binary data into binary data --- the
@@ -643,7 +769,7 @@

Check_Type(str, T_STRING);

- while ((result = PQgetResult(conn))) {
+ while ((result = PQgetResult(conn)) != NULL) {
PQclear(result);
}

@@ -2558,6 +2684,7 @@
rb_define_singleton_method(rb_cPGconn, "escape_bytea", pgconn_s_escape_bytea, 1);
rb_define_singleton_method(rb_cPGconn, "unescape_bytea", pgconn_s_unescape_bytea, 1);
rb_define_singleton_method(rb_cPGconn, "translate_results=", pgconn_s_translate_results_set, 1);
+ rb_define_singleton_method(rb_cPGconn, "quote_ident", pgconn_s_quote_ident, 1);

rb_define_const(rb_cPGconn, "CONNECTION_OK", INT2FIX(CONNECTION_OK));
rb_define_const(rb_cPGconn, "CONNECTION_BAD", INT2FIX(CONNECTION_BAD));
@@ -2592,6 +2719,13 @@
rb_define_method(rb_cPGconn, "transaction_status", pgconn_transaction_status, 0);
rb_define_method(rb_cPGconn, "protocol_version", pgconn_protocol_version, 0);
rb_define_method(rb_cPGconn, "server_version", pgconn_server_version, 0);
+ rb_define_method(rb_cPGconn, "escape", pgconn_escape, 1);
+ rb_define_method(rb_cPGconn, "escape_bytea", pgconn_escape_bytea, 1);
+ rb_define_method(rb_cPGconn, "unescape_bytea", pgconn_s_unescape_bytea, 1);
+ rb_define_method(rb_cPGconn, "quote", pgconn_quote, 1);
+ rb_define_method(rb_cPGconn, "quote_ident", pgconn_s_quote_ident, 1);
+ rb_define_alias(rb_cPGconn, "format", "quote");
+
/* following line is for rdoc */
/* rb_define_method(rb_cPGconn, "lastval", pgconn_lastval, 0); */


--3MwIy2ne0vdjdPXF--
 
J

Jason LaRiviere

Kevin said:
Sometimes the gem install deletes the binary after creating it, but
I just go into that directory and 'make' it again.

Ha. I've never come across that before, but indeed that is what happened
with the gem install on os x. I'm now off postgres-pr and back on
ruby-postgres all around.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top