⚡ Bolt: Implement prepared statements for Insert, Update, and Delete#96
Conversation
- Refactored InsertQuery, UpdateQuery, and DeleteQuery builders to separate compilation from execution. - Added Prepare() method to IUD builders returning specialized prepared query types. - Implemented PreparedInsertQuery, PreparedUpdateQuery, and PreparedDeleteQuery in pkg/rain/prepared_exec.go. - Added support for Exec() and Scan() (RETURNING) on prepared IUD queries. - Added unit tests for compilation and integration tests for execution. This change brings Rain ORM closer to Drizzle feature parity by providing a consistent API for prepared statements across all core query types. Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Greptile SummaryThis PR extends prepared statement support from
Confidence Score: 5/5Safe to merge; the refactoring preserves all existing validation logic and the new prepared-exec path follows the established SelectQuery pattern faithfully. The SQL generation refactoring is a straightforward extraction with no observable behaviour changes — all table-nil, view, unbounded, and empty-assignment guards remain in place. The new PreparedXxx types follow the same structure as the existing PreparedSelectQuery. The one structural gap (missing RETURNING guard in Scan) was identified and discussed in a prior review thread. pkg/rain/prepared_exec.go — Scan methods on the three prepared types; the RETURNING guard present on the non-prepared counterparts is absent here.
|
| Filename | Overview |
|---|---|
| pkg/rain/prepared_exec.go | New file introducing PreparedInsertQuery, PreparedUpdateQuery, PreparedDeleteQuery with Exec, Scan, and Close methods; Scan methods lack the RETURNING guard present on the non-prepared counterparts (already flagged in previous thread) |
| pkg/rain/query_insert.go | Refactored ToSQL into compile()/writeValuesSQL()/writeSelectSQL(); Prepare() method added; logic is semantically equivalent to the original with no regressions observed |
| pkg/rain/query_update.go | Refactored ToSQL into compile()/writeSQL(); Prepare() method added; equivalent semantics to original code |
| pkg/rain/query_delete.go | Refactored ToSQL into compile()/writeSQL(); Prepare() method added; equivalent semantics to original code |
| pkg/rain/sqlite_integration_test.go | Adds TestSQLiteIntegrationPreparedExecQueries covering end-to-end Exec and Scan paths for Insert, Update, and Delete prepared queries |
Sequence Diagram
sequenceDiagram
participant User
participant Builder as InsertQuery / UpdateQuery / DeleteQuery
participant compile as compile()
participant Runner as preparingQueryRunner
participant Prepared as PreparedXxxQuery
participant DB as database/sql
User->>Builder: .Set(...).Where(...).Prepare(ctx)
Builder->>compile: compile()
compile-->>Builder: compiledQuery (sql, argPlan, hasNames)
Builder->>Runner: prepareContext(ctx, sql)
Runner->>DB: db.PrepareContext(ctx, sql)
DB-->>Runner: "*sql.Stmt"
Runner-->>Builder: "*sql.Stmt"
Builder-->>User: "*PreparedXxxQuery"
User->>Prepared: "Exec(ctx, PreparedArgs{...})"
Prepared->>Prepared: compiled.bind(args) → []any
Prepared->>DB: stmt.ExecContext(ctx, bound...)
DB-->>User: sql.Result, error
User->>Prepared: "Scan(ctx, PreparedArgs{...}, &dest)"
Prepared->>Prepared: compiled.bind(args) → []any
Prepared->>DB: stmt.QueryContext(ctx, bound...)
DB-->>Prepared: "*sql.Rows"
Prepared->>Prepared: scanRowsAgainstTable(rows, dest, table)
Prepared-->>User: error
User->>Prepared: Close()
Prepared->>Prepared: closeOnce.Do → stmt.Close()
Prepared-->>User: error
Reviews (2): Last reviewed commit: "feat(rain): implement prepared statement..." | Re-trigger Greptile
| func (p *PreparedInsertQuery) Scan(ctx context.Context, args PreparedArgs, dest any) (err error) { | ||
| bound, err := p.compiled.bind(args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| rows, err := p.stmt.QueryContext(ctx, bound...) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer closeRows(rows, &err) | ||
|
|
||
| return scanRowsAgainstTable(rows, dest, p.table) | ||
| } |
There was a problem hiding this comment.
Missing RETURNING guard in prepared Scan methods
The non-prepared InsertQuery.Scan(), UpdateQuery.Scan(), and DeleteQuery.Scan() all guard against being called without a RETURNING clause by checking len(q.returning) == 0 and returning a clear "rain: insert scan requires RETURNING" error. The three prepared Scan methods have no such check. If a user prepares an INSERT/UPDATE/DELETE without .Returning(...) and then calls Scan(), the statement executes but the database returns no columns; scanRowsAgainstTable then fails with a confusing low-level error (e.g., a scan plan mismatch) rather than the clear sentinel the caller would expect.
Fix: add a hasReturning bool field to each prepared type, set it to len(q.returning) > 0 in the respective Prepare() method, and check it at the top of each Scan(). The same gap exists on lines 76–89 (PreparedUpdateQuery.Scan) and lines 121–134 (PreparedDeleteQuery.Scan).
Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/rain/prepared_exec.go
Line: 31-44
Comment:
**Missing RETURNING guard in prepared Scan methods**
The non-prepared `InsertQuery.Scan()`, `UpdateQuery.Scan()`, and `DeleteQuery.Scan()` all guard against being called without a `RETURNING` clause by checking `len(q.returning) == 0` and returning a clear `"rain: insert scan requires RETURNING"` error. The three prepared `Scan` methods have no such check. If a user prepares an `INSERT`/`UPDATE`/`DELETE` without `.Returning(...)` and then calls `Scan()`, the statement executes but the database returns no columns; `scanRowsAgainstTable` then fails with a confusing low-level error (e.g., a scan plan mismatch) rather than the clear sentinel the caller would expect.
Fix: add a `hasReturning bool` field to each prepared type, set it to `len(q.returning) > 0` in the respective `Prepare()` method, and check it at the top of each `Scan()`. The same gap exists on lines 76–89 (`PreparedUpdateQuery.Scan`) and lines 121–134 (`PreparedDeleteQuery.Scan`).
How can I resolve this? If you propose a fix, please make it concise.- Refactored InsertQuery, UpdateQuery, and DeleteQuery builders to separate compilation from execution. - Added Prepare() method to IUD builders returning specialized prepared query types. - Implemented PreparedInsertQuery, PreparedUpdateQuery, and PreparedDeleteQuery in pkg/rain/prepared_exec.go. - Added support for Exec() and Scan() (RETURNING) on prepared IUD queries. - Used named return parameters in Scan() to ensure proper defer error handling. - Added unit tests for compilation and integration tests for execution. This change brings Rain ORM closer to Drizzle feature parity by providing a consistent API for prepared statements across all core query types. Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
This PR implements support for prepared statements in
INSERT,UPDATE, andDELETEqueries, mirroring the existing support forSELECTqueries.Key Changes:
InsertQuery,UpdateQuery, andDeleteQueryinto unexportedcompile()andwriteSQL()methods. This aligns them with the architectural pattern used bySelectQuery.Prepare(ctx context.Context)method to each IUD builder.PreparedInsertQuery,PreparedUpdateQuery, andPreparedDeleteQueryin a new filepkg/rain/prepared_exec.go.Exec(ctx, args)for standard execution andScan(ctx, args, dest)for queries with aRETURNINGclause.rows.Close()were potentially swallowed inScanmethods by adopting named return parameters.This implementation provides a consistent, type-safe, and performant way to reuse modification queries with different arguments.
PR created automatically by Jules for task 5865195085006225899 started by @cungminh2710