Skip to content

feat(orm): select_sub / add_select subquery-as-column + table() setter#170

Open
tmgbedu wants to merge 1 commit into
mainfrom
task/orm-select-sub-895b
Open

feat(orm): select_sub / add_select subquery-as-column + table() setter#170
tmgbedu wants to merge 1 commit into
mainfrom
task/orm-select-sub-895b

Conversation

@tmgbedu

@tmgbedu tmgbedu commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up B from splitting #168. Adds subquery-as-column support to the async query builder:

posts = await Post.query().select("id", "title").select_sub(
    Category.query().select("name").where_column("categories.id", "posts.category_id").limit(1),
    "category_name",
).get()
  • select_sub(builder, alias) and add_select(alias, subquery) render a subquery as an aliased column — (subquery) AS alias — with bindings emitted in SELECT position (before WHERE). add_select also accepts a callable that builds the subquery on a fresh builder.
  • table(name) sets the target table on a bare builder. This is the setter the *Through / BelongsToMany with-count query paths already reference (previously dangling), so it resolves those calls and backs the callable form of add_select.

Existing string-based select() is unchanged.

Tests

  • tests/masoniteorm/query/grammars/test_select_sub_grammar.py — grammar-level to_sql()/to_qmark() + binding-order assertions across all 4 dialects (incl. MSSQL TOP n); covers builder-instance and callable forms and the non-builder TypeError.
  • tests/masoniteorm/sqlite/builder/test_sqlite_select_sub.py — real sqlite-backed correlated-column reads plus the table() setter.

Gates

  • uv run pytest --ignore=tests/masoniteorm/postgres --cov → 1808 passed / 7 skipped, coverage 78.88% (≥ fail_under 68).
  • ruff check + ruff format --check clean.
  • Zero regression to existing string-based select().

Relationship to other PRs

Independent of #168 and #169 (correlated subqueries here use the 2-arg where_column already on main). Recommended overall order: #168#169 → this.

Add subquery-as-column support to the async query builder:

- select_sub(builder, alias) and add_select(alias, subquery) render a
  subquery as an aliased column, (subquery) AS alias, with bindings emitted
  in SELECT position (before WHERE). add_select also accepts a callable that
  builds the subquery on a fresh builder.
- table(name) sets the target table on a bare builder; this is the setter
  the *Through/BelongsToMany with-count query paths already reference, so it
  resolves those dangling calls, and it backs the callable form of
  add_select.

Existing string-based select() is unchanged. Covered by grammar-level
to_sql()/to_qmark() with binding-order assertions across all four dialects
(SQLite, MySQL, Postgres, MSSQL, incl. MSSQL TOP n), plus a real
sqlite-backed suite.
@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tmgbedu

tmgbedu commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

✅ Code Review Verdict: APPROVE (posted as comment — GH blocks self-approval)

Reviewed for correctness, binding order, alias emission, and regression. All gates reproduced locally on the PR branch.

Verified

  • SELECT-position binding order is correct. BaseGrammar._compile_select evaluates process_columns(...) before process_wheres(...) (kwargs eval left-to-right), so subquery bindings precede WHERE bindings. get_bindings() == [1, "active"] is genuinely exercised and correct. No accumulation across re-compiles: each to_qmark() builds a fresh grammar and the subquery's to_qmark() reassigns (not +=) its _bindings.
  • Alias emission (subquery) AS alias correct across all 4 dialects incl. MSSQL TOP n.
  • add_select callable form constructs QueryBuilder(connection, grammar, processor) matching the real __init__ signature; non-builder input raises TypeError as tested.
  • table() setter is genuinely new (no collision with any mixin/method on QueryBuilder) and correctly backs the previously-dangling chained .table() calls in HasManyThrough/HasOneThrough/BelongsToMany with-count paths. Full relationship suite passes.
  • Reuses existing SubGroupExpression rather than inventing a new mechanism — good altitude.
  • Zero regression / test quality: real sqlite-backed correlated reads + grammar-level assertions, no hollow mocks.

Gates reproduced (PR worktree)

  • uv run pytest --ignore=tests/masoniteorm/postgres1808 passed / 7 skipped (matches report)
  • New tests: 9 passed / 20 subtests
  • ruff check + ruff format --check → clean

Non-blocking notes

  • Alias in (subquery) AS {alias} is emitted unquoted — consistent with existing correlated where_column fragments; fine for normal identifiers. Not a blocker.
  • With-count paths now activated rely on the general suite for coverage; dedicated tests tracked in backlog #886.

No correctness bugs found. Recommend merge (per split order #168#169 → this).

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