-
Notifications
You must be signed in to change notification settings - Fork 107
BE-188: Use ColumnReference in expressions and complete JoinClause
#7963
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
BE-188: Use ColumnReference in expressions and complete JoinClause
#7963
Conversation
## Documentation improvements Add comprehensive rustdoc comments for query expression types: - `ColumnReference`: Document fields and conversion from `Column` - `TableReference`: Document schema hierarchy and alias behavior - `JoinType`: Document join semantics and add `reverse()` method - `JoinFrom`: Document table vs subquery sources ## JOIN expression refactoring Convert `JoinExpression` from struct to enum to properly model PostgreSQL's different join types with type-level invariants: - `Conditioned`: INNER/LEFT/RIGHT/FULL OUTER with explicit ON conditions - `Cross`: CROSS JOIN (cartesian product, no conditions) - `Natural`: NATURAL JOIN (implicit column matching) This prevents invalid states like CROSS JOIN with conditions at compile time. ## Bug fixes - Fix schema information loss in `JoinFrom::transpile()` by calling `table.transpile()` instead of `table.name.transpile()` - Add assertion to catch empty conditions in `Conditioned` variant - Add helper methods `from_item()` and `from_item_mut()` for ergonomic access ## Tests - Add tests for CROSS and NATURAL JOIN transpilation - Add test to verify empty conditions panic behavior - Add tests for `reference_table()` with/without alias - All 146 existing tests continue to pass Clippy clean with proper use of `Self`, merged match arms, and `#[expect]` attributes for justified warnings.
Improve type safety for SQL wildcard handling by:
- Converting `SelectExpression` from struct to enum with:
- `Expression { expression, alias }` for regular expressions
- `Asterisk(Option<TableReference>)` for wildcards (* or table.*)
- Removing `Asterisk` from `ColumnName` enum to prevent it appearing
in invalid SQL contexts (WHERE clauses, JOIN conditions, etc.)
- Adding `Expression::RowExpansion` for PostgreSQL's `(row).*` syntax
used to expand composite types into columns
- Removing unused `Expression::FieldAccess` variant
This change enforces at the type level that asterisk wildcards can only
appear in SELECT clauses or as row expansions, not in expressions where
they would be invalid SQL.
All 146 tests pass.
Change `ColumnName` from a public enum to an opaque struct with: - Private `ColumnNameImpl` enum containing `Static(Column)` and `Dynamic(Identifier)` - `From` implementations for `Column` and `Identifier` - `Transpile` and `Debug` implementations - String-based equality and hashing (consistent with `TableName`) This provides a cleaner API by hiding implementation details while maintaining the same functionality. The internal representation can now be changed without breaking external code. Updated all usage sites in tests and `Column::aliased` to use `ColumnName::from`.
Align join clause naming with SQL keywords for consistency: - Conditioned → On (matches JOIN ... ON syntax) - Natural (already matches NATURAL JOIN) - Cross (already matches CROSS JOIN) This creates consistent, clear naming where all variants directly reflect the SQL syntax they represent.
Extend JoinClause enum with a Using variant that supports PostgreSQL's
USING clause syntax for joins on matching column names.
The USING clause specifies column names that must exist in both tables
and PostgreSQL joins rows where these columns have equal values. The
specified columns appear only once in the result set.
Transpiles to: `<JOIN TYPE> "table" USING ("col1", "col2")`
This completes the set of JOIN clause types:
- On: explicit conditions with ON clause
- Using: matching column names with USING clause
- Cross: cartesian product
- Natural: implicit matching columns
- Add `lateral` boolean field to `JoinFrom::Subquery` for LATERAL subquery support - Implement `join_using_alias` field in `JoinClause::Using` for PostgreSQL USING alias syntax - Allow empty `conditions` in `On` variant to transpile to `ON TRUE` (cartesian product) - Allow empty `columns` in `Using` variant to transpile to `ON TRUE` for consistency with NATURAL JOIN behavior - Add `indoc` as dev-dependency for improved test readability - Add comprehensive tests for LATERAL subqueries and join_using_alias - Remove clippy warning suppression for empty USING joins (now semantically valid) All 150 tests passing with 0 clippy warnings.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
ColumnReference in expressionsColumnReference in expressions and complete JoinClause
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7963 +/- ##
==========================================
+ Coverage 55.77% 56.06% +0.28%
==========================================
Files 1116 1118 +2
Lines 101509 102147 +638
Branches 4701 4722 +21
==========================================
+ Hits 56616 57264 +648
+ Misses 44228 44210 -18
- Partials 665 673 +8 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Graphite Automations"Request backend reviewers once CI passes" took an action on this PR • (10/31/25)1 reviewer was added to this PR based on Tim Diekmann's automation. "Request Rust reviewers once CI passes" took an action on this PR • (10/31/25)1 reviewer was added to this PR based on Tim Diekmann's automation. "Request DevOps reviewers once CI passes" took an action on this PR • (11/01/25)1 reviewer was added to this PR based on Tim Diekmann's automation. |
Merge activity
|
98db011 to
eacb301
Compare
ColumnReference in expressions and complete JoinClauseColumnReference in expressions and merge JoinClause into FromItem
ColumnReference in expressions and merge JoinClause into FromItemColumnReference in expressions and complete JoinClause
eacb301 to
eacc409
Compare
eacc409 to
08767dd
Compare
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1001 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1526 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 51 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 107 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 25 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |

🌟 What is the purpose of this PR?
This PR refactors the PostgreSQL query builder to improve the representation of table and column references. It introduces a more structured approach to handling SQL identifiers, table references, and join clauses, making the code more maintainable and type-safe.
🔍 What does this change?
JoinExpressionwith a more flexibleJoinClauseenumSelectExpressionto properly handle both regular expressions and asterisk wildcards🛡 What tests cover this?