-
Notifications
You must be signed in to change notification settings - Fork 818
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
Add QueryRewriter interface (and named arguments) #1186
Comments
Added Example usage: conn.Query(ctx, "select * from widgets where foo = @foo and bar = @bar", pgx.NamedArgs{"foo": 1, "bar": 2})) |
should use in prepare sql statement ?? |
This wouldn't work with explicit prepared statements. But with pgx automatically prepares and caches statements so explicit prepared statements generally shouldn't be used. |
Hi @jackc I have just been looking at NamedArgs and wondered what would happen if there was a typo meaning a mismatch between the placeholders in the SQL and the map keys of NamedArgs. I was hoping I would get an error in the second example below. Is this expected behaviour, or a oversight? First I get 323 rows, as expected: // This works as expected.
rows, _ := conn.Query(ctx, `
SELECT COUNT(*)
FROM cron
WHERE school_id = @school_id
`,
pgx.NamedArgs{"school_id": 2})
total, err := pgx.CollectOneRow(rows, pgx.RowTo[int32])
is.NoErr(err)
t.Logf("Got %d rows", total) // Got 323 rows Now check if we get an error if we mismatch the NamedArgs map key with the placeholder. rows, _ = conn.Query(ctx, `
SELECT COUNT(*)
FROM cron
WHERE school_id = @school_id`,
pgx.NamedArgs{"school_iD": 2}) // The key does not match the placeholder
total, err = pgx.CollectOneRow(rows, pgx.RowTo[int32])
is.NoErr(err)
t.Logf("Got %d rows", total) // Got 0 rows No error, no rows. |
You're welcome!
It's expected, though I can see the argument against it. There are two cases:
Both cases could be an error. But both cases can also be convenient. 1. allows optional fields that default to NULL. 2. automatically filters extra values when the original map came from an untrusted source. I suppose a |
@jackc Thanks for your feedback.
I would prefer it be an error unless the map keys are exactly equal to the placeholders. I took a look at implementing your StrictNamedArgs idea but I cannot see that it will be possible without changing the QueryRewriter interface. Can I communicate error back? if queryRewriter != nil {
sql, args = queryRewriter.RewriteQuery(ctx, c, sql, args)
} Would you consider changing the QueryRewriter interface so that RewriteQuery returns an error which would be returned on Conn.Query() / Rows.Err() allowing for QueryRewriter implementations to report an error? |
Technically that would be a breaking change. But given that v5 is so new and this is likely not been used by external code I think that breaking semver would be justifiable here. |
@mwchambers I added the error return to RewriteQuery in 7d3b9c1. |
@jackc Thanks for this! This is really quite useful. I can now make any custom 'sql builder' type implement pgx.QueryRewriter and pass the SQL builder instance directly to pgx with the ability for the builder to return an error. As an example: // sqlb would be similar to your pgsql package,
// but returns one Query type which implements pgx.QueryRewriter
// It sets an error if the placeholders to not match 1:1
query := sqlb.New("SELECT foo, bar FROM baz WHERE x = ? AND y = ?", 1, 2)
// No need for pgsql.Build() or similar to get sql and args
rows, _ := conn.Query(ctx, "sql param is not used", query)
// Or...
query := sqlb.Insert("widget", sqlb.RowMap{"name": "Mark", "total": 42})
_, err = conn.Exec(ctx, "", query) I have decided against using NamedArgs / StrictNamedArgs after applying it to parts of some existing code to see how it worked out. It would add a lot of repetition of the column names and not add much benefit. I thought it would be useful when editing params in the middle of large query so you don't have to realign $3, $4 etc in the SQL, keeping the diff a lot cleaner. Your pgsql ? approach solves this issue without having to name all the columns (though perhaps not as safe as no sqlLexer) So again, thank you. This gives us lots of flexibility for experimentation. |
A long-standing request is to support named arguments. e.g.
The actual PostgreSQL
$1
style placeholders are not bad when there are only a few. But it rapidly gets difficult to manage when there are a lot of them especially when certain arguments are used multiple times.I propose adding
QueryRewriter
interface defined something like:If a
QueryRewriter
was the first argument to a query itsRewriteQuery
method would be called and the query would actually run on the results.This would provide an means to add a
type NamedArgs map[string]any
that implements named arguments viaQueryRewriter
. In this way named arguments could be added fairly seamlessly.In addition,
QueryRewriter
might be useful for a number of other utilities. e.g. anInsertArgs
that is used to create the column list and the values clause of an insert statement. Not sure if anything pastNamedArgs
would actually be useful or a good idea but the flexibility of this approach would enable all sorts of experiments like that without needing to put them in pgx.The text was updated successfully, but these errors were encountered: