Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ Placeholders in an SQL statement take any of the following formats:
* `?`
* `?_nnn_`
* `:_word_`
* `:_nnn_`
* `$_word_`
* `$_nnn_`
* `@_word_`
* `@_nnn_`


Where _n_ is an integer, and _word_ is an alpha-numeric identifier (or
Expand Down
39 changes: 39 additions & 0 deletions ext/sqlite3/statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,44 @@ bind_parameter_count(VALUE self)
return INT2NUM(sqlite3_bind_parameter_count(ctx->st));
}

/** call-seq: stmt.params
*
* Return the list of named alphanumeric parameters in the statement.
* This returns a list of strings.
* The values of this list can be used to bind parameters
* to the statement using bind_param. Numeric and anonymous parameters
* are ignored.
*
*/
static VALUE
named_params(VALUE self)
{
sqlite3StmtRubyPtr ctx;
TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);

REQUIRE_LIVE_DB(ctx);
REQUIRE_OPEN_STMT(ctx);

int param_count = sqlite3_bind_parameter_count(ctx->st);
VALUE params = rb_ary_new2(param_count);

// The first host parameter has an index of 1, not 0.
for (int i = 1; i <= param_count; i++) {
const char *name = sqlite3_bind_parameter_name(ctx->st, i);
// If parameters of the ?NNN/$NNN/@NNN/:NNN form are used
// there may be gaps in the list.
if (name) {
// We ignore numeric parameters
int n = atoi(name + 1);
if (n == 0) {
Comment on lines +491 to +492
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not quite right -- I can have a named parameter that happens to be an integer. The SQLite docs say:

In the SQL statement text input to sqlite3_prepare_v2() and its variants, literals may be replaced by a parameter that matches one of the following templates:

  • ?
  • ?NNN
  • :VVV
  • @vvv
  • $VVV

In the templates above, NNN represents an integer literal, and VVV represents an alphanumeric identifier. The values of these parameters (also called "host parameter names" or "SQL parameters") can be set using the sqlite3_bind_*() routines defined here.

(emphasis is mine). More concretely, this passing test demonstrates what I'm talking about:

    def test_named_number_bind
      stmt = SQLite3::Statement.new(@db, "select :1")
      stmt.bind_param("1", "hello")

      result = nil
      stmt.each { |x| result = x }
      assert_equal ["hello"], result

      stmt.close
    end

That :1 is a named param, but this code will ignore it.

VALUE param = interned_utf8_cstr(name + 1);
rb_ary_push(params, param);
}
}
}
return rb_obj_freeze(params);
}

enum stmt_stat_sym {
stmt_stat_sym_fullscan_steps,
stmt_stat_sym_sorts,
Expand Down Expand Up @@ -689,6 +727,7 @@ init_sqlite3_statement(void)
rb_define_method(cSqlite3Statement, "column_name", column_name, 1);
rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1);
rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0);
rb_define_method(cSqlite3Statement, "named_params", named_params, 0);
rb_define_method(cSqlite3Statement, "sql", get_sql, 0);
rb_define_method(cSqlite3Statement, "expanded_sql", get_expanded_sql, 0);
#ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME
Expand Down
6 changes: 6 additions & 0 deletions test/test_statement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ def test_named_bind_not_found
stmt.close
end

def test_params
stmt = SQLite3::Statement.new(@db, "select ?1, :foo, ?, $bar, @zed, ?250, @999")
assert_equal ["foo", "bar", "zed"], stmt.named_params
Comment on lines +260 to +261
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my previous comment, I think this test needs to be updated and expanded to include numeric names:

Suggested change
stmt = SQLite3::Statement.new(@db, "select ?1, :foo, ?, $bar, @zed, ?250, @999")
assert_equal ["foo", "bar", "zed"], stmt.named_params
stmt = SQLite3::Statement.new(@db, "select ?1, :foo, ?, $bar, @zed, ?250, @999, :123, $777")
assert_equal ["foo", "bar", "zed", "999", "123", "777"], stmt.named_params

stmt.close
end

def test_each
r = nil
@stmt.each do |row|
Expand Down
Loading