New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Postgres: allow enabling prepared statements for specific queries when globally disabled. #36795
Conversation
|
oh sorry, I must have misinterpreted the comments on the other thread. I will endeavour to write a test! Will probably look something like #24893 (comment) |
I think the three-argument form of If
Huh? 😕 |
ah that's helpful, thanks.
Yes, that seems kind of the point? Maybe see response below about zero binds = no prepare question.
Good catch, it isn't anymore, can be removed I think.
I thought about this and came out on the side of "yes". I feel like if I'm using such a low level API, I'd expect it to "do what I say, I know what I'm doing".
oh look at that, it totally works. Sorry, no idea why I thought that wasn't possible! |
While not necessarily unreasonable, is that what our own existing caller(s) are expecting? AFAICS, this change flips the method from having two modes "use prepare if you like" and "definitely don't use prepare", to instead "definitely do use prepare" and "maybe use prepare". While I see how that might fit better with a mental model that has prepare defaulted to off, I'm not sure it's obviously superior, nor obviously what the method should always have done. (Indeed, cbcdecd makes the "definitely don't" sound rather important.) It feels like we're heading towards a point where we end up with a much more low level API than we had before, where its purpose seemed to be encapsulating the config check. I do see the callers look to be doing a fair bit of that work themselves these days... but that just leaves me wondering if it's even worth having such a method, whose only real job is to call one of two other methods, chosen based on a caller-supplied boolean argument. 🤷🏻♂️ |
Good question. I tried to find existing uses. I think It appears the intent here though was that for any query that shouldn't be prepared, it is caught by the visitor and setting of This usage in Interestingly, the sqlite3 adapter appears to respect
I started confusing myself in trying to respond, so let's make a table: Before
After
I think it's quite possible that this behaviour is fine (see above analysis), but for "smallest possible change" I strictly only need the
per above
I don't feel I have enough context to comment intelligently on this point. So where does that leave us? I'm honestly not sure :( Some other alternatives that may be worth considering:
Thank you all for your engagement 🙏 |
Quick note because I only just re-connected these dots: while I haven't dug through history to confirm, I'm pretty sure the "never prepare empty binds" behaviour is an earlier attempt at cbcdecd. Given that, and the newer more complete behaviour in that subsequent commit, I think ditch the empty-bind check & special case entirely. As we've noted, the only caller that passes Combining those two points, I think the condition does in fact collapse to: if prepare
result = exec_cache(sql, name, binds)
else
result = exec_no_cache(sql, name, binds)
end With that done, I'm strongly tempted to inline those two methods -- when they were split they were very different, but over time they've accumulated a lot of identical parallel content -- but that can be a thought for another day. |
Thanks for writing out the tables, btw -- I'd managed to confuse myself into thinking that [true, false, non-empty] was changing to prepare, despite having earlier noted that the |
2303074
to
8c19327
Compare
…n disabled globally. There is no way to do this otherwise.
8c19327
to
a3b355e
Compare
Updated with test coverage. It doesn't quite simplify down to two branches because of |
After #36949, would it be sufficient merely to add a def prepared_statement
@prepared_statement_status.bind(true) { yield }
end |
I think my suggestion will work, with an additional change. Right now, we have this code in if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statement_status = Concurrent::ThreadLocalVar.new(true)
@visitor.extend(DetermineIfPreparableVisitor)
else
@prepared_statement_status = Concurrent::ThreadLocalVar.new(false)
end Specifically, To test this, I added a test to def test_prepared_statements_can_be_forced
conn = Computer.connection
rel = Computer.where(id: 1)
unprepared_sql = conn.to_sql(rel.arel)
prepared_sql = conn.prepared_statement do
conn.to_sql(rel.arel)
end
assert_not unprepared_sql.include?('$1')
assert prepared_sql.include?('$1')
end This seems like a pretty janky way to detect if a statement was prepared or not, but I'm not sure what the proper way is to do this. But the real issue here is that this test will pass even if |
Your logic sounds good to me, but I don't have a lot of context here. (Nor do I know the answers to your questions.) |
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
23:54, 17 декабря 2019 г., "rails-bot[bot]" <notifications@github.com>:This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.
—You are receiving this because you are subscribed to this thread.Reply to this email directly, view it on GitHub, or unsubscribe.
Отправлено из мобильной Яндекс.Почты: http://yandex
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
Summary
When prepared statements are disabled globally for postgres, the
prepare
argument toexec_query
(and any others) is now respected to enable them to be selectively enabled.I don't believe there is a way to do this otherwise. I believe this is a bug in the current method, given the expectations implied by its method signature.
Per discussion in #24893 this is a private API, so I didn't want to add
extra test coverage for it to imply otherwise. (Separate to this PR, I would probably make an argument that this API is effectively public given widespread usage of
exec_query
.)Other Information
We are currently using a monkey patch to apply this in our code base. To provide more context on this PR, and also for the benefit of anyone who finds this issue, I've include below our justification and specific patch for 5.2.3: