Conversation
Support variadic execute with $1, $2, ... placeholders for safe parameter
binding. This allows calling PostgreSQL functions without defining custom
types.
Example:
conn->execute("SELECT my_func($1, $2)", arg1, arg2);
Supported types: string, numeric, bool, optional, nullptr/nullopt for NULL.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@mcraveiro , what exactly is the use case for this? I think for something like SELECT and INSERT we already use stored procedures under-the-hood. In my view, executing raw strings should really be the exception, not the norm. So I would be interested what specific problem you are trying to solve here to figure out whether we could find a solution that does not rely on raw strings. If we were to find out that the problem cannot be solved without raw strings, I would be willing to merge this. |
|
Hi, thanks for the review. So, we have a large number of utility stored procs we run on our database. These will increase in the future but for now we do things such as provisioning a new tenant, de-provisioning an existing tenant, de-provision all test tenents and so on. These are "true" stored procedures, in the sense that we are not really trying to return a result set. In addition, sometimes we need to supply arguments (e.g. tenant id). Sometimes it's just a function with no arguments. Basically all of our non-CRUD infrastructure relies on these. How I bumped into this approach: I was using session to execute SQL. Claude started to use libpq to address Gemini code review comments about SQL injection as we were using raw strings concatenated to call the procs. Then we did some investigation to figure out the idiomatic way to do this in sqlgen. We explored adding types, but it does not make a lot of sense to create types for these procs because we'll end up with a myriad of tiny types just to call the proc and retrieve the result. So I thought this change could handle both issues at the same time - I don't want to use raw libpq if I can avoid it. But yes, this is useful only to call "real" stored procedures. |
|
hm, actually the more we discuss this the more I am thinking maybe the correct solution is to add types for each stored proc ( create or replace function ores_iam_purge_tenant_fn(
p_tenant_id uuid
) returns void as $$
...I could create a select ores_iam_purge_tenant_fn("SOME_TENANT");Am I going in the right direction? |
In-line SQL and Setting up RLSAnother example of why we use inline SQL is RLS [1]: conn.execute("SELECT set_config('app.current_tenant_id', '" + tenant_id + "', false)"); Which raises another point. When using RLS we need to setup variables for policies on each connection. This is a bit of a problem when using a pool because every time we get a connection off of the pool, we need to remember to run this little snippet. For now I have created my own connection pool which ensures it's setup correctly: template <class Connection>
class tenant_aware_pool {
public:
using init_callback = std::function<void(Connection&)>;
tenant_aware_pool(sqlgen::ConnectionPool<Connection> pool,
init_callback on_acquire)
: pool_(std::move(pool)), on_acquire_(std::move(on_acquire)) {}
sqlgen::Result<sqlgen::Ref<sqlgen::Session<Connection>>> acquire() noexcept {
auto session_result = pool_.acquire();
if (session_result && on_acquire_) {
// Run init SQL on the connection
auto& session = *session_result;
session->execute(/* init SQL */); // or call the callback
}
return session_result;
}
size_t available() const { return pool_.available(); }
size_t size() const { return pool_.size(); }
private:
sqlgen::ConnectionPool<Connection> pool_;
init_callback on_acquire_;
};
// Custom session() overload for our wrapper
template <class Connection>
sqlgen::Result<sqlgen::Ref<sqlgen::Session<Connection>>> session(
tenant_aware_pool<Connection>& pool) noexcept {
return pool.acquire();
} |
Summary
Add support for parameterized queries using PostgreSQL's
$1, $2, ...placeholder syntax. This enables safe parameter binding for:std::optionalorstd::nulloptAPI
Implementation
PostgresV2Result::make()overload usingPQexecParamsexecute()template toConnectionwithto_param()helperexecute_params()method handles the actual libpq callTest plan
execute_with_string_params- string and int parametersexecute_with_null_param- NULL viastd::optionalexecute_with_numeric_params- int, double, boolexecute_call_function- calling PL/pgSQL function🤖 Generated with Claude Code