On Oct 12, 12:06 pm, Nate Wiger <[email protected]> wrote:
> I'm not sure I'd call 30% a modest improvement. :-) That's a huge
> difference when you're talking lots of transactions.
I would refer to anything less than a binary order of magnitude as a
huge improvement, but I'll agree that it's more than modest. :)
> Regarding the API, I just want to point out that 'id = :id' is not my
> syntax - it's pervasive in DB adapters and ORM's alike. Hence the
> reason I naively tried it in Sequel and stumbled on it raising an OCI
> error ("not all variables bound").
It's not used in PostgreSQL to my knowledge (which uses $1), nor on
MySQL (which uses ?). I think SQLite allows it, and it looks like
Oracle does as well. So I wouldn't say that it's pervasive in DB
adapters.
I'm not sure when ActiveRecord started supporting it, but it think it
was after 2.0.
> I think there are a couple things that are getting intertwined:
>
> 1) My proposal to extend the Sequel syntax - bind vars or not - to
> support :named placeholders like other DB toolsets
True. Using a placeholder literal string with a hash of conditions
would be a feature by itself.
> 2) Your concern that bind variables can be enabled/disabled per-query
> since some adapters (or queries) may not want to use them
I'll go out on a limb here and state that due to the necessary
overhead, my theory is that using bind variables with Sequel will
decrease performance in the general case on most adapters.
> 3) Backwards compatibility/spec/adapter concerns (I'm a huge proponent
> of backwards compat, so let's assume that one will be solved)
I'd say that's a huge assumption given some of what you are
suggesting, but OK. :)
> Regarding #1, let me try to show why this is really powerful. Here's
> a real snippet from our codebase:
>
> curs = conn.parse("SELECT lb.* FROM
> (SELECT rownum as rn, a.* FROM
> (SELECT * FROM #{table_name}
> WHERE game_type_id = :game_type_id
> AND platform_id = :platform_id
> AND rank >= :rank-:num_above_below
> AND rank <= :rank+:num_above_below
> ORDER BY rank) a) lb,
> (SELECT rn from
> (SELECT rownum as rn, b.player_id FROM
> (SELECT player_id FROM #{table_name}
> WHERE game_type_id = :game_type_id
> AND platform_id = :platform_id
> AND rank >= :rank-:num_above_below
> AND rank <= :rank+:num_above_below
> ORDER BY rank) b)
> WHERE player_id = :player_id) p_rownum
> WHERE lb.rn >=
> p_rownum.rn-:num_above_below AND lb.rn <= p_rownum.rn+:num_above_below
> ORDER BY lb.rank", sql_args])
>
> sql_args = {:game_type_id => game_type_id,
> :platform_id => platform_id,
> :player_id => player_id,
> :rank => player.rank,
> :num_above_below => num_above_below}
>
> You'll notice that the bind params are reused multiple times in the
> statement - :player_id, :num_above_below, etc. (That query is highly-
> tuned c/o my team's DBA)
The Sequel way to write that would be to use Sequel's DSL. However, I
can see your point in regards to having existing SQL with named
placeholders and wanting to easily port your code to Sequel. Allowing
placeholder literal strings to take a hash of arguments would make
that case much easier, so I'll support including this in Sequel.
> When that query was first written, the programmer used ?'s and
> positional binding, and it was a mess. You couldn't tell which ? went
> with which parameter, and it actually had a bug since the wrong one
> was bound to the wrong position, and it took a good long while to
> figure out.
That's completely understandable. Like I mentioned, the Sequel way to
write that would be to use the DSL, not interpolated strings, which
avoids the issue in a different way.
> So in this case, your suggestion of wrapping the existing API:
>
> dataset.bindfilt('id = :id', :id=>1) == dataset.filter('id = ?', :
> $id).bind(:id=>1)
>
> Doesn't work, since it's the *:named placeholder itself* that's so
> important. Replacing it with ?'s results in the problems I mentioned
> above.
That's because you don't understand how Sequel works. Sequel would
literalize this:
dataset.filter('id = ?', :$id).bind(:id=>1)
as:
id = :id
Example using SQLite, which uses named placeholders:
$ ruby -I lib bin/sequel -E sqlite:/
Your database is stored in DB...
irb(main):001:0> DB.create_table(:a){Integer :a}
I, [2009-10-12T12:55:01.252667 #8359] INFO -- : CREATE TABLE `a` (`a`
integer)
=> []
irb(main):002:0> DB[:a].filter('a = ?', :$blah).call
(:select, :blah=>1)
I, [2009-10-12T12:55:28.105581 #8359] INFO -- : SELECT * FROM `a`
WHERE (a = :blah); {"blah"=>1}
=> []
irb(main):003:0>
With the named placeholder support, you could always translate it this
way instead this instead:
dataset.bindfilt('id = :id', :id=>1) =>
dataset.filter('id = :id', :id=>$id).bind(:id=>1)
However, there would be no reason to do it that way, as that would
just be slower.
> 2) Regarding enabling/disabling bind vars, as a variant of your idea,
> how about this:
>
> # No bind variables (default behavior remains unchanged)
> User.filter('id = ?', 14).all
> User.filter('id = :id', :id => 14).all
> User.filter('lower(city) like lower(:search) or lower(state) like
> lower(:search)', :search => string).all
That wouldn't have any problems.
> # Enable bind variables by calling bind()
> User.filter('id = ?', 14).bind.all
> User.filter('id = :id', :id => 14).bind.all
I think that would be problematic to implement correctly. What's
wrong with adding a new method that does exactly what you want?
> # bind() accepts delayed bind args too
> User.filter('lower(city) like lower(:search) or lower(state) like
> lower(:search)').bind(:search => string).all
I've already explained that a single string passed to filter becomes a
literal string (not a placeholder literal string), and Sequel never
parses literal strings, so this can't work.
> Additionally, how about an adapter setting (off by default) to
> globally always use bind()?
>
> DB = Sequel.connect(..., :logger => ..., :always_bind_variables =>
> true) # or whatever
>
> Then a person could still disable it per-query with bind(false):
>
> User.filter('id = ?', 14).bind(false).all
>
> This way we get the "best of all worlds."
Assuming that you could get 2) working correctly (a huge assumption),
that seems OK.
> Finally, there's no reason why the existing API couldn't remain around
> for compat, and this stuff could all be shortcuts to it. But
> the :named placeholders would have to go in core since they're in the
> sql string like ? is.
>
> Thoughts?
I still think the suggestions I made in my previous post stand, with
some small additions:
1) Add support to Sequel's Oracle adapter to support bound variables
and prepared statements, using Sequel's existing API.
2) Add support for placeholder literal strings to accept a single hash
of arguments and use named placeholders in that case.
3) Add support for Dataset#bind, for setting bound variables before
calling Dataset#call.
4) Create a Sequel extension that adds a method allowing nicer syntax
for bound variables/prepared statements. If this extension meets the
5 points I mentioned earlier, I wouldn't have a problem shipping it
with Sequel.
I wouldn't have a problem completing 2) and 3) myself, and I could
probably help you with 4) if not complete it myself. Since I don't
have an Oracle installation, you'll have to complete 1) yourself,
though I'd be happy to answer any Sequel-related questions.
Jeremy
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"sequel-talk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/sequel-talk?hl=en
-~----------~----~----~----~------~----~------~--~---