diff --git a/FAQ.md b/FAQ.md index eb43875b..a3886605 100644 --- a/FAQ.md +++ b/FAQ.md @@ -122,15 +122,17 @@ Placeholders in an SQL statement take any of the following formats: * `?` * `?_nnn_` * `:_word_` +* `$_word_` +* `@_word_` -Where _n_ is an integer, and _word_ is an alpha-numeric identifier (or -number). When the placeholder is associated with a number, that number -identifies the index of the bind variable to replace it with. When it -is an identifier, it identifies the name of the corresponding bind -variable. (In the instance of the first format--a single question -mark--the placeholder is assigned a number one greater than the last -index used, or 1 if it is the first.) +Where _n_ is an integer, and _word_ is an alpha-numeric identifier(or number). +When the placeholder is associated with a number (only in case of `?_nnn_`), +that number identifies the index of the bind variable to replace it with. +When it is an identifier, it identifies the name of the corresponding bind +variable. (In the instance of the first format--a single question mark--the +placeholder is assigned a number one greater than the last index used, or 1 +if it is the first.) For example, here is a query using these placeholder formats: diff --git a/ext/sqlite3/statement.c b/ext/sqlite3/statement.c index 9dedcd2d..fae06ed1 100644 --- a/ext/sqlite3/statement.c +++ b/ext/sqlite3/statement.c @@ -460,6 +460,41 @@ 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); + // We ignore numbered parameters (starting with ?) + // And null values, since there can be gaps in the list + if (name && *name != '?') { + // We ignore numeric parameters + 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, @@ -689,6 +724,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 diff --git a/test/test_statement.rb b/test/test_statement.rb index b6a55001..b273bc4e 100644 --- a/test/test_statement.rb +++ b/test/test_statement.rb @@ -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, :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|