Michael Granger avatar Michael Granger committed 7c313c2

Split the dual-mode PG::Connection#exec into #exec and #exec_params.

PG::Connection#exec should be backward compatible with old code: it just passes
control to #exec_params directly instead of calling PQexecParams() itself.

Thanks to Aaron Patterson for the idea.

Comments (1)

  1. Michael Granger author

    This splits the PQexecParams() branch out of PG::Connection#exec into a new #exec_params method, with code to make #exec still work with older code that passes parameters to it.

    Any thoughts, concerns, etc?

Files changed (2)

ext/pg_connection.c

 /* :TODO: get_ssl */
 
 
+static VALUE pgconn_exec_params( int, VALUE *, VALUE );
 
 /*
  * call-seq:
- *    conn.exec(sql [, params, result_format ] ) -> PG::Result
- *    conn.exec(sql [, params, result_format ] ) {|pg_result| block }
+ *    conn.exec(sql) -> PG::Result
+ *    conn.exec(sql) {|pg_result| block }
  *
  * Sends SQL query request specified by _sql_ to PostgreSQL.
  * Returns a PG::Result instance on success.
  * On failure, it raises a PG::Error.
  *
- * +params+ is an optional array of the bind parameters for the SQL query.
+ * For backward compatibility, if you pass more than one parameter to this method,
+ * it will call #exec_params for you. New code should explicitly use #exec_params if
+ * argument placeholders are used.
+ *
+ * If the optional code block is given, it will be passed <i>result</i> as an argument,
+ * and the PG::Result object will  automatically be cleared when the block terminates.
+ * In this instance, <code>conn.exec</code> returns the value of the block.
+ */
+static VALUE
+pgconn_exec(int argc, VALUE *argv, VALUE self)
+{
+	PGconn *conn = pg_get_pgconn(self);
+	PGresult *result = NULL;
+	VALUE rb_pgresult;
+
+	/* If called with no parameters, use PQexec */
+	if ( argc == 1 ) {
+		Check_Type(argv[0], T_STRING);
+
+		result = gvl_PQexec(conn, StringValuePtr(argv[0]));
+		rb_pgresult = pg_new_result(result, self);
+		pg_result_check(rb_pgresult);
+		if (rb_block_given_p()) {
+			return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult);
+		}
+		return rb_pgresult;
+	}
+	
+	/* Otherwise, just call #exec_params instead for backward-compatibility */
+	else {
+		return pgconn_exec_params( argc, argv, self );
+	}
+
+}
+
+
+/*
+ * call-seq:
+ *    conn.exec_params(sql, params[, result_format ] ) -> PG::Result
+ *    conn.exec_params(sql, params[, result_format ] ) {|pg_result| block }
+ *
+ * Sends SQL query request specified by +sql+ to PostgreSQL using placeholders
+ * for parameters.
+ *
+ * Returns a PG::Result instance on success. On failure, it raises a PG::Error.
+ *
+ * +params+ is an array of the bind parameters for the SQL query.
  * Each element of the +params+ array may be either:
  *   a hash of the form:
  *     {:value  => String (value of bind parameter)
  * In this instance, <code>conn.exec</code> returns the value of the block.
  */
 static VALUE
-pgconn_exec(int argc, VALUE *argv, VALUE self)
+pgconn_exec_params( int argc, VALUE *argv, VALUE self )
 {
 	PGconn *conn = pg_get_pgconn(self);
 	PGresult *result = NULL;
 
 	rb_scan_args(argc, argv, "12", &command, &params, &in_res_fmt);
 
-	Check_Type(command, T_STRING);
-
-	/* If called with no parameters, use PQexec */
-	if(NIL_P(params)) {
-		result = gvl_PQexec(conn, StringValuePtr(command));
-		rb_pgresult = pg_new_result(result, self);
-		pg_result_check(rb_pgresult);
-		if (rb_block_given_p()) {
-			return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult);
+	/*
+	 * Handle the edge-case where the caller is coming from #exec, but passed an explict +nil+
+	 * for the second parameter.
+	 */
+	if ( NIL_P(params) ) {
+		return pgconn_exec( 1, argv, self );
 		}
-		return rb_pgresult;
-	}
-
-	/* If called with parameters, and optionally result_format,
-	 * use PQexecParams
-	 */
+
 	Check_Type(params, T_ARRAY);
 
-	if(NIL_P(in_res_fmt)) {
+	if ( NIL_P(in_res_fmt) ) {
 		resultFormat = 0;
 	}
 	else {
 
 	gc_array = rb_ary_new();
 	rb_gc_register_address(&gc_array);
+
 	sym_type = ID2SYM(rb_intern("type"));
 	sym_value = ID2SYM(rb_intern("value"));
 	sym_format = ID2SYM(rb_intern("format"));
 	paramValues = ALLOC_N(char *, nParams);
 	paramLengths = ALLOC_N(int, nParams);
 	paramFormats = ALLOC_N(int, nParams);
-	for(i = 0; i < nParams; i++) {
+
+	for ( i = 0; i < nParams; i++ ) {
 		param = rb_ary_entry(params, i);
 		if (TYPE(param) == T_HASH) {
 			param_type = rb_hash_aref(param, sym_type);
 
 	rb_pgresult = pg_new_result(result, self);
 	pg_result_check(rb_pgresult);
+
 	if (rb_block_given_p()) {
-		return rb_ensure(rb_yield, rb_pgresult,
-			pg_result_clear, rb_pgresult);
+		return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult);
 	}
+
 	return rb_pgresult;
 }
 
 	/******     PG::Connection INSTANCE METHODS: Command Execution     ******/
 	rb_define_method(rb_cPGconn, "exec", pgconn_exec, -1);
 	rb_define_alias(rb_cPGconn, "query", "exec");
+	rb_define_method(rb_cPGconn, "exec_params", pgconn_exec_params, -1);
 	rb_define_method(rb_cPGconn, "prepare", pgconn_prepare, -1);
 	rb_define_method(rb_cPGconn, "exec_prepared", pgconn_exec_prepared, -1);
 	rb_define_method(rb_cPGconn, "describe_prepared", pgconn_describe_prepared, 1);

spec/pg/connection_spec.rb

 	end
 
 
+	it "supports parameters passed to #exec (backward compatibility)" do
+		@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
+
+		res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
+		res.values.should == [ ['Wally'], ['Sally'] ]
+	end
+
+	it "supports explicitly calling #exec_params" do
+		@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
+		@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
+
+		res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
+		res.values.should == [ ['Wally'], ['Sally'] ]
+	end
+
+
 	it "can wait for NOTIFY events" do
 		@conn.exec( 'ROLLBACK' )
 		@conn.exec( 'LISTEN woo' )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.