Skip to content

Conversation

captn3m0
Copy link

Closes #627

Implemented named_params directly in C. Not a C programmer, mistakes are mine. Learned about @ and $ prefixes while reading the docs, so added them to the test as well.

However, this implementation ignores sqlite3_bind_parameter_index entirely, which is only used internally so shouldn't be an issue(?). As of now, the index of the parameter in the result might not match the value returned by sqlite3_bind_parameter_index in some cases.

But this feels better than having to pad the result with nulls.

Once this is finalized, I can update the changelog if needed

@captn3m0
Copy link
Author

captn3m0 commented Sep 2, 2025

@flavorjones mind taking a look?

Copy link
Member

@flavorjones flavorjones left a comment

Choose a reason for hiding this comment

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

@captn3m0 Thanks for this! I've kicked off CI.

I'd like to see a test for the behavior of this code when given ?NNN parameters. Do you think the method should return those parameters? I'm not sure what happens if you try to bind a variable to a name like ?13.

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++) {
Copy link
Member

Choose a reason for hiding this comment

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

Just a note for posterity: When given a parameter like ?32760 this code is going to iterate over the numbers in the gap calling sqlite3_bind_parameter_name for each one. That's pretty inefficient, but I don't see any way to avoid it given the set of functions SQLite provides.

ref: https://sqlite.org/c3ref/bind_blob.html

Copy link
Author

Choose a reason for hiding this comment

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

Can add a note about it to the docs.

@captn3m0
Copy link
Author

I'm not sure what happens if you try to bind a variable to a name like ?13.

https://sqlite.org/forum/forumpost/533576942b17bf66 is the best explanation I could find.

? - means to use the next available parameter number that is one greater than any parameter number previously used starting at 1 if this is the first one.
?NNN - means parameter at location number NNN
?NNN (:VVV, @ VVV and $VVV are same) - means parameter at location number NNN.

Nothing special about 3 digits either, ?1 binds as the first param, and ?1111 happily binds as the 1111th parameter.

sqlite3_bind_parameter_name returns the numeric value itself as a string for these parameters. So this test passes on the current code:

stmt = SQLite3::Statement.new(@db, "select ?1")
stmt.bind_param(1, "foo")
assert_equal ["1"], stmt.named_params
stmt.each do |row|
  assert_equal ["foo"], row
end

My first thought is that these aren't really named parameters and we should just drop them. But then, if you were using a statement like: "select ?2, ?3", you'd be back to the same problem this PR is trying to solve for these ?NNN params - no way to discover the params based off just bind_parameter_count.

One way would be to return a mixed array? [2, "foo", "bar", 3], so that the values in the array can be directly used against bind_params. And perhaps change the name merely stmt.params.

@captn3m0
Copy link
Author

Spent a bit more time at the problem, and it looks like the list bindable numbered params problem is unsolvable. The parameter_count is the max_index, and anything below that is considered bindable. For a statement like "SELECT ?, ?, ?50", you can bind all params from 1-50.

And there's no way to differentiate and get a result that says [1,2,50].

One way would be to return a mixed array? [2, "foo", "bar", 3], so that the values in the array can be directly used against bind_params. And perhaps change the name merely stmt.params.

I wrote this implementation, which works well as long as params are either numbered or named. Once you add anonymous params to the mix, it goes haywire.

Suggestion: named_params should only support really named params (which are alphanumeric tokens), and drop any numeric params. For those, a simple loop from 1-bind_parameter_count is the best you can do which is already support.

numeric unused params are undistinguishable from
numeric bindable parameters. So a simple loop from
1-bind_parameter_count is the best you can do.

This means .named_params can be focused on the
truly named parameters only, which is what This
commit does by dropping numeric parameters
@captn3m0
Copy link
Author

Pushed a change to ignore numeric and anonymous parameters:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expose statement bind parameters
2 participants