diff --git a/FAQ.md b/FAQ.md index eb43875b..b55ff35c 100644 --- a/FAQ.md +++ b/FAQ.md @@ -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 diff --git a/ext/sqlite3/statement.c b/ext/sqlite3/statement.c index 9dedcd2d..81c5f8c6 100644 --- a/ext/sqlite3/statement.c +++ b/ext/sqlite3/statement.c @@ -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) { + 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 +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 diff --git a/test/test_statement.rb b/test/test_statement.rb index b6a55001..e3c27d72 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") + assert_equal ["foo", "bar", "zed"], stmt.named_params + stmt.close + end + def test_each r = nil @stmt.each do |row|