From: Jeff Davis Date: 2007-01-20T10:38:47+09:00 Subject: "postgres" module needs work, author gone, patch included --=-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, dave@cherryville.org, but got no response. I tried to contact the previous maintainer, noborus@zetabits.com, 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[i]); - if (OBJ_TAINTED(RARRAY(obj)->ptr[i])) 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[i] == '\'' || ptr[i] == '\\') + str[j++] = '\\'; + str[j++] = ptr[i]; + } + 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[i] == '"') + str[j++] = '"'; + else if(ptr[i] == '\0') + rb_raise(rb_ePGError, "Identifier cannot contain NULL bytes"); + str[j++] = ptr[i]; + } + 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--