Skip to content

Add SelectQuery.First and Selective Relation Columns#124

Closed
cungminh2710 wants to merge 3 commits into
mainfrom
feat/select-first-and-selective-relations-9720018403007912893
Closed

Add SelectQuery.First and Selective Relation Columns#124
cungminh2710 wants to merge 3 commits into
mainfrom
feat/select-first-and-selective-relations-9720018403007912893

Conversation

@cungminh2710

Copy link
Copy Markdown
Contributor

This PR introduces two key enhancements to Rain ORM:

  1. SelectQuery.First(ctx, dest): A convenience method that executes the query with an implicit LIMIT 1 and scans the result into dest. It returns sql.ErrNoRows if no result is found, matching Drizzle's behavior.

  2. Selective Relation Columns: Enhanced RelationConfig to support a Columns field, allowing users to specify which columns to load for a relation. The implementation automatically ensures that relationship mapping keys (source/target columns) are included in the underlying SQL query to preserve data mapping integrity.

Test coverage includes both unit tests for SQL generation and integration tests using SQLite.


PR created automatically by Jules for task 9720018403007912893 started by @cungminh2710

- Implement SelectQuery.First(ctx, dest) for ergonomic single-row fetching.
- Add Columns field to RelationConfig for selective column loading in relational queries.
- Ensure join keys are automatically included in relational subqueries.
- Add unit and integration tests.

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 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 @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

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-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two features: a First(ctx, dest) convenience method on SelectQuery that applies an implicit LIMIT 1 before scanning, and selective relation column loading via a new Columns []schema.Expression field on RelationConfig, with auto-inclusion of the join-mapping key column (ensureTargetColumnSelected).

  • First is a one-liner clone-and-delegate that correctly propagates sql.ErrNoRows for struct destinations through the existing scanRowsWithRelations path; integration tests cover both the success and no-rows cases.
  • Selective relation columns work correctly for single-level relations: ensureTargetColumnSelected guards the target FK column so data mapping survives user-specified column subsets.
  • Gap: ensureTargetColumnSelected only covers the current relation's TargetColumn; when a relation with Columns specified is also a parent of nested child relations, the child's SourceColumn (e.g. posts.id for a comments child) is not auto-added to the query, causing nested relation loading to silently return empty or zero-keyed results.

Confidence Score: 4/5

Safe to merge for all single-level selective relation usage; the nested relation + selective columns combination produces silent data loss and should be fixed or documented before production use.

The First method is a clean, minimal addition with correct ErrNoRows propagation. The selective-columns feature works correctly for the tested single-level case. However, when Columns is set on a relation that also has child relations loaded, the child relation's source column (e.g. the PK of the intermediate table) is not auto-included in the SQL, so all nested reads silently return empty or zero-keyed results with no error returned to the caller.

pkg/rain/relation_loading.go — specifically the loadRelatedRows and loadRelatedManyToManyRows functions where selective column enforcement happens without accounting for child relation source columns.

Important Files Changed

Filename Overview
pkg/rain/query_select.go Adds First(ctx, dest) convenience method that clones the query, applies LIMIT 1, and delegates to Scan. Implementation is clean and minimal.
pkg/rain/relation_loading.go Adds RelationConfig.Columns selective loading with ensureTargetColumnSelected guard. The guard correctly covers single-level loading but omits auto-inclusion of child-relation SourceColumns, which causes silent empty/wrong nested results when Columns is set on a non-leaf relation.
pkg/rain/query_select_test.go Adds TestSelectFirst which validates LIMIT 1 SQL generation; does not invoke First() directly but tests the underlying mechanism; reasonable given prior review feedback.
pkg/rain/sqlite_integration_test.go Adds integration tests for selective relation columns (single-level, with FK field in dest struct) and for First including ErrNoRows validation; both tests are well-structured and cover the happy path and the no-rows case.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant SelectQuery
    participant loadRelationNode
    participant loadRelatedRows
    participant ensureTargetColumnSelected
    participant DB

    Caller->>SelectQuery: First(ctx, dest)
    SelectQuery->>SelectQuery: clone().Limit(1)
    SelectQuery->>DB: Scan(ctx, dest)
    DB-->>SelectQuery: rows
    SelectQuery-->>Caller: nil or sql.ErrNoRows

    Caller->>SelectQuery: Scan + Relation(posts, Columns:[title])
    SelectQuery->>DB: "SELECT * FROM users"
    DB-->>SelectQuery: parent rows
    SelectQuery->>loadRelationNode: load posts node
    loadRelationNode->>loadRelatedRows: sourceKeys, Columns:[title]
    loadRelatedRows->>ensureTargetColumnSelected: ensure posts.user_id
    loadRelatedRows->>DB: SELECT title, user_id FROM posts WHERE user_id IN (...)
    DB-->>loadRelatedRows: "post rows (id=0 in struct)"
    loadRelatedRows-->>loadRelationNode: relatedRows
    Note over loadRelationNode: node.children: posts.id missing
    loadRelationNode->>DB: SELECT FROM comments WHERE post_id IN (0)
    DB-->>loadRelationNode: empty result - silent data loss
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant SelectQuery
    participant loadRelationNode
    participant loadRelatedRows
    participant ensureTargetColumnSelected
    participant DB

    Caller->>SelectQuery: First(ctx, dest)
    SelectQuery->>SelectQuery: clone().Limit(1)
    SelectQuery->>DB: Scan(ctx, dest)
    DB-->>SelectQuery: rows
    SelectQuery-->>Caller: nil or sql.ErrNoRows

    Caller->>SelectQuery: Scan + Relation(posts, Columns:[title])
    SelectQuery->>DB: "SELECT * FROM users"
    DB-->>SelectQuery: parent rows
    SelectQuery->>loadRelationNode: load posts node
    loadRelationNode->>loadRelatedRows: sourceKeys, Columns:[title]
    loadRelatedRows->>ensureTargetColumnSelected: ensure posts.user_id
    loadRelatedRows->>DB: SELECT title, user_id FROM posts WHERE user_id IN (...)
    DB-->>loadRelatedRows: "post rows (id=0 in struct)"
    loadRelatedRows-->>loadRelationNode: relatedRows
    Note over loadRelationNode: node.children: posts.id missing
    loadRelationNode->>DB: SELECT FROM comments WHERE post_id IN (0)
    DB-->>loadRelationNode: empty result - silent data loss
Loading

Reviews (3): Last reviewed commit: "refactor(rain): remove FirstToSQL test h..." | Re-trigger Greptile

Comment thread pkg/rain/query_select.go
Comment thread pkg/rain/query_select.go Outdated
Comment thread pkg/rain/query_select_test.go
google-labs-jules Bot and others added 2 commits June 15, 2026 22:43
- Implement SelectQuery.First(ctx, dest) for ergonomic single-row fetching.
- Add Columns field to RelationConfig for selective column loading in relational queries.
- Ensure join keys are automatically included in relational subqueries.
- Add unit and integration tests.
- Fix linting and formatting issues found in CI.

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
- Removed FirstToSQL test helper from query_select.go as requested.
- Updated query_select_test.go to verify First() behavior using standard methods.
- Fixed linting issue (unused struct) in query_select_test.go.
- Verified all tests pass with make precommit.

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant