Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions pkg/rain/query_compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ func (q compiledQuery) literalArgs() ([]any, error) {
if q.hasNames {
return nil, ErrPreparedArgsRequired
}
// OPTIMIZATION: Return a fresh copy of the pre-calculated arguments to avoid
// shared state footguns if the caller modifies the slice, while still
// avoiding the overhead of rebuilding the slice from argPlan.
return append([]any(nil), q.args...), nil
// OPTIMIZATION: Return the pre-calculated arguments directly. Since q.args
// is a fresh slice created during compilation and query runners do not
// modify it, we can safely avoid the redundant copy.
return q.args, nil
Comment on lines +37 to +40
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Removed copy changes the slice-ownership contract for prepared queries

For prepared types (PreparedInsertQuery, PreparedUpdateQuery, PreparedDeleteQuery, PreparedSelectQuery), the compiledQuery is compiled once and stored in the struct; every subsequent call to Exec/Scan calls bind() on that stored value. With this change, the !hasNames fast-path now hands back a reference to the same backing array (p.compiled.args) on every call. That's safe today because no current caller writes to the returned slice before passing it to ExecContext/QueryContext. However, the old comment explicitly named this as protection against "shared state footguns", and removing it silently drops the guard. If any future code path modifies bound in-place between bind() and the DB call (e.g., driver-specific wrapping, middleware, or retry logic), it will corrupt the stored compiledQuery.args for all subsequent calls β€” a data-race-free but logically corrupting mutation with no immediate panic. A // NOTE: callers must not modify the returned slice comment would preserve the intent without the allocation cost.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/rain/query_compile.go
Line: 37-40

Comment:
**Removed copy changes the slice-ownership contract for prepared queries**

For prepared types (`PreparedInsertQuery`, `PreparedUpdateQuery`, `PreparedDeleteQuery`, `PreparedSelectQuery`), the `compiledQuery` is compiled once and stored in the struct; every subsequent call to `Exec`/`Scan` calls `bind()` on that stored value. With this change, the `!hasNames` fast-path now hands back a reference to the same backing array (`p.compiled.args`) on every call. That's safe today because no current caller writes to the returned slice before passing it to `ExecContext`/`QueryContext`. However, the old comment explicitly named this as protection against "shared state footguns", and removing it silently drops the guard. If any future code path modifies `bound` in-place between `bind()` and the DB call (e.g., driver-specific wrapping, middleware, or retry logic), it will corrupt the stored `compiledQuery.args` for all subsequent calls β€” a data-race-free but logically corrupting mutation with no immediate panic. A `// NOTE: callers must not modify the returned slice` comment would preserve the intent without the allocation cost.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

}

func (q compiledQuery) bind(args PreparedArgs) ([]any, error) {
if !q.hasNames {
if len(args) > 0 {
return nil, fmt.Errorf("rain: unexpected prepared args for query without placeholders")
}
// OPTIMIZATION: Return a fresh copy of the pre-calculated arguments to avoid
// shared state footguns if the caller modifies the slice.
return append([]any(nil), q.args...), nil
// OPTIMIZATION: Return the pre-calculated arguments directly when there
// are no named placeholders to bind.
return q.args, nil
}

seen := make(map[string]struct{}, len(args))
Expand Down