Skip to content

Conversation

@TimDiekmann
Copy link
Member

@TimDiekmann TimDiekmann commented Nov 1, 2025

🌟 What is the purpose of this PR?

Refactor the SQL query generation to use a recursive join structure instead of a flat list of joins. This improves the representation of complex join trees and simplifies the query building process.

🔍 What does this change?

  • Replace JoinClause and JoinFrom with a recursive FromItem enum that can represent arbitrary join trees
  • Modify SelectStatement to use a single from field with nested joins instead of separate from and joins fields
  • Add a join() method to SelectStatement to build join trees in a more intuitive way
  • Convert table hook functions to return conditions instead of adding them directly
  • Add TableSample support for sampling rows from tables

🛡 What tests cover this?

  • Existing tests have been updated to work with the new structure
  • New tests added for FromItem and TableSample functionality

@github-actions github-actions bot added area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team labels Nov 1, 2025
@TimDiekmann TimDiekmann changed the title BE-188: Merge JoinClause into FromItem BE-189: Merge JoinClause into FromItem Nov 1, 2025
@codecov
Copy link

codecov bot commented Nov 1, 2025

Codecov Report

❌ Patch coverage is 95.32710% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.11%. Comparing base (519794b) to head (fe2b23a).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...e/src/store/postgres/query/expression/from_item.rs 97.29% 14 Missing and 7 partials ⚠️
...postgres-store/src/store/postgres/query/compile.rs 86.07% 8 Missing and 3 partials ⚠️
...store/src/store/postgres/query/statement/insert.rs 0.00% 8 Missing ⚠️
...e/src/store/postgres/query/expression/join_type.rs 71.42% 4 Missing ⚠️
...store/src/store/postgres/query/statement/select.rs 95.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7980      +/-   ##
==========================================
+ Coverage   55.80%   56.11%   +0.30%     
==========================================
  Files        1117     1118       +1     
  Lines      101979   102262     +283     
  Branches     4742     4721      -21     
==========================================
+ Hits        56911    57385     +474     
+ Misses      44403    44205     -198     
- Partials      665      672       +7     
Flag Coverage Δ
apps.hash-ai-worker-ts 1.32% <ø> (ø)
apps.hash-api 0.00% <ø> (ø)
local.claude-hooks ?
local.hash-backend-utils 4.08% <ø> (ø)
local.hash-graph-sdk 10.88% <ø> (ø)
local.hash-isomorphic-utils 0.00% <ø> (ø)
rust.hash-graph-api 2.89% <ø> (ø)
rust.hash-graph-postgres-store 25.58% <95.32%> (+2.25%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@graphite-app
Copy link
Contributor

graphite-app bot commented Nov 1, 2025

Graphite Automations

"Request backend 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.

"Request Rust 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.

@TimDiekmann TimDiekmann force-pushed the t/be-189-merge-joinclause-into-fromitem branch from 3f99fed to 540987e Compare November 2, 2025 23:40
Base automatically changed from t/be-188-use-columnreference-in-expressions-and-complete-joinclause to main November 3, 2025 09:03
@TimDiekmann TimDiekmann force-pushed the t/be-189-merge-joinclause-into-fromitem branch from 540987e to fe2b23a Compare November 3, 2025 09:03
@graphite-app
Copy link
Contributor

graphite-app bot commented Nov 3, 2025

Merge activity

  • Nov 3, 9:04 AM UTC: Graphite rebased this pull request, because this pull request is set to merge when ready.

@TimDiekmann TimDiekmann added this pull request to the merge queue Nov 3, 2025
Merged via the queue into main with commit b9c5597 Nov 3, 2025
76 checks passed
@TimDiekmann TimDiekmann deleted the t/be-189-merge-joinclause-into-fromitem branch November 3, 2025 09:46
@github-actions
Copy link
Contributor

github-actions bot commented Nov 3, 2025

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$29.2 \mathrm{ms} \pm 169 \mathrm{μs}\left({\color{gray}0.116 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.70 \mathrm{ms} \pm 24.7 \mathrm{μs}\left({\color{red}5.02 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1001 $$14.3 \mathrm{ms} \pm 80.6 \mathrm{μs}\left({\color{gray}0.493 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$45.3 \mathrm{ms} \pm 321 \mathrm{μs}\left({\color{gray}-0.089 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$15.7 \mathrm{ms} \pm 111 \mathrm{μs}\left({\color{gray}-0.693 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1526 $$25.7 \mathrm{ms} \pm 166 \mathrm{μs}\left({\color{gray}0.147 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$28.9 \mathrm{ms} \pm 154 \mathrm{μs}\left({\color{lightgreen}-38.616 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.98 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{lightgreen}-62.239 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$15.7 \mathrm{ms} \pm 87.9 \mathrm{μs}\left({\color{lightgreen}-51.110 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.97 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{red}8.02 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.00 \mathrm{ms} \pm 18.8 \mathrm{μs}\left({\color{gray}1.70 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 51 $$3.52 \mathrm{ms} \pm 22.4 \mathrm{μs}\left({\color{red}8.07 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.46 \mathrm{ms} \pm 36.4 \mathrm{μs}\left({\color{gray}2.68 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.70 \mathrm{ms} \pm 25.3 \mathrm{μs}\left({\color{gray}-1.621 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 107 $$4.37 \mathrm{ms} \pm 22.1 \mathrm{μs}\left({\color{gray}-3.092 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.66 \mathrm{ms} \pm 31.4 \mathrm{μs}\left({\color{red}13.0 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.55 \mathrm{ms} \pm 24.6 \mathrm{μs}\left({\color{red}6.64 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.25 \mathrm{ms} \pm 28.5 \mathrm{μs}\left({\color{red}7.53 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.66 \mathrm{ms} \pm 14.0 \mathrm{μs}\left({\color{gray}4.01 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.69 \mathrm{ms} \pm 14.6 \mathrm{μs}\left({\color{red}7.95 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1 $$2.77 \mathrm{ms} \pm 16.1 \mathrm{μs}\left({\color{gray}3.85 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$2.98 \mathrm{ms} \pm 17.8 \mathrm{μs}\left({\color{gray}2.35 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.80 \mathrm{ms} \pm 13.7 \mathrm{μs}\left({\color{gray}2.47 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$3.08 \mathrm{ms} \pm 15.8 \mathrm{μs}\left({\color{gray}-0.028 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.14 \mathrm{ms} \pm 17.7 \mathrm{μs}\left({\color{red}12.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.81 \mathrm{ms} \pm 18.9 \mathrm{μs}\left({\color{red}14.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 25 $$3.01 \mathrm{ms} \pm 19.1 \mathrm{μs}\left({\color{red}16.7 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.54 \mathrm{ms} \pm 26.2 \mathrm{μs}\left({\color{red}14.9 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.14 \mathrm{ms} \pm 20.4 \mathrm{μs}\left({\color{red}16.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 26 $$3.31 \mathrm{ms} \pm 21.6 \mathrm{μs}\left({\color{red}15.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.45 \mathrm{ms} \pm 22.4 \mathrm{μs}\left({\color{red}15.8 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.02 \mathrm{ms} \pm 18.0 \mathrm{μs}\left({\color{red}13.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.24 \mathrm{ms} \pm 20.3 \mathrm{μs}\left({\color{red}13.4 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$40.9 \mathrm{ms} \pm 212 \mathrm{μs}\left({\color{gray}0.512 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$88.7 \mathrm{ms} \pm 404 \mathrm{μs}\left({\color{gray}-0.892 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$44.8 \mathrm{ms} \pm 196 \mathrm{μs}\left({\color{gray}-2.341 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$47.3 \mathrm{ms} \pm 194 \mathrm{μs}\left({\color{gray}2.08 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$56.1 \mathrm{ms} \pm 297 \mathrm{μs}\left({\color{gray}-3.223 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$41.8 \mathrm{ms} \pm 201 \mathrm{μs}\left({\color{lightgreen}-7.175 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$500 \mathrm{ms} \pm 943 \mathrm{μs}\left({\color{gray}-1.268 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$97.4 \mathrm{ms} \pm 444 \mathrm{μs}\left({\color{lightgreen}-7.198 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$86.5 \mathrm{ms} \pm 436 \mathrm{μs}\left({\color{gray}-0.365 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$298 \mathrm{ms} \pm 837 \mathrm{μs}\left({\color{gray}-1.554 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$15.3 \mathrm{ms} \pm 70.9 \mathrm{μs}\left({\color{lightgreen}-8.033 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$16.2 \mathrm{ms} \pm 93.6 \mathrm{μs}\left({\color{gray}0.664 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$16.4 \mathrm{ms} \pm 85.7 \mathrm{μs}\left({\color{gray}0.870 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$16.1 \mathrm{ms} \pm 87.8 \mathrm{μs}\left({\color{gray}-2.513 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$20.1 \mathrm{ms} \pm 133 \mathrm{μs}\left({\color{gray}2.05 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$16.4 \mathrm{ms} \pm 96.7 \mathrm{μs}\left({\color{gray}4.31 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$15.7 \mathrm{ms} \pm 69.4 \mathrm{μs}\left({\color{gray}-1.069 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$16.2 \mathrm{ms} \pm 85.6 \mathrm{μs}\left({\color{gray}1.93 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$16.4 \mathrm{ms} \pm 76.3 \mathrm{μs}\left({\color{gray}-2.992 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$24.4 \mathrm{ms} \pm 200 \mathrm{μs}\left({\color{gray}0.163 \mathrm{\%}}\right) $$ 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 $$31.3 \mathrm{ms} \pm 282 \mathrm{μs}\left({\color{gray}-4.090 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$31.3 \mathrm{ms} \pm 316 \mathrm{μs}\left({\color{gray}-3.877 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$31.5 \mathrm{ms} \pm 349 \mathrm{μs}\left({\color{gray}-4.981 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$31.7 \mathrm{ms} \pm 263 \mathrm{μs}\left({\color{gray}-3.726 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$31.3 \mathrm{ms} \pm 312 \mathrm{μs}\left({\color{gray}-4.048 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$31.9 \mathrm{ms} \pm 307 \mathrm{μs}\left({\color{gray}-0.751 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$31.6 \mathrm{ms} \pm 282 \mathrm{μs}\left({\color{gray}-4.051 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$32.3 \mathrm{ms} \pm 354 \mathrm{μs}\left({\color{lightgreen}-5.664 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$32.1 \mathrm{ms} \pm 287 \mathrm{μs}\left({\color{gray}-0.451 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$9.01 \mathrm{ms} \pm 38.1 \mathrm{μs}\left({\color{lightgreen}-5.616 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$58.0 \mathrm{ms} \pm 292 \mathrm{μs}\left({\color{gray}-1.511 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$109 \mathrm{ms} \pm 390 \mathrm{μs}\left({\color{gray}-3.459 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$63.1 \mathrm{ms} \pm 335 \mathrm{μs}\left({\color{gray}-4.419 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$71.9 \mathrm{ms} \pm 282 \mathrm{μs}\left({\color{gray}-3.106 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$80.8 \mathrm{ms} \pm 317 \mathrm{μs}\left({\color{gray}-3.932 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$87.4 \mathrm{ms} \pm 333 \mathrm{μs}\left({\color{gray}-2.958 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$52.3 \mathrm{ms} \pm 260 \mathrm{μs}\left({\color{lightgreen}-5.620 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$81.7 \mathrm{ms} \pm 345 \mathrm{μs}\left({\color{lightgreen}-6.055 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$59.5 \mathrm{ms} \pm 303 \mathrm{μs}\left({\color{lightgreen}-5.093 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$66.9 \mathrm{ms} \pm 303 \mathrm{μs}\left({\color{lightgreen}-5.401 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$70.2 \mathrm{ms} \pm 432 \mathrm{μs}\left({\color{gray}-3.947 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$69.5 \mathrm{ms} \pm 293 \mathrm{μs}\left({\color{lightgreen}-6.943 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$142 \mathrm{ms} \pm 499 \mathrm{μs}\left({\color{gray}2.71 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$136 \mathrm{ms} \pm 495 \mathrm{μs}\left({\color{gray}-0.979 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$44.3 \mathrm{ms} \pm 221 \mathrm{μs}\left({\color{lightgreen}-57.750 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$594 \mathrm{ms} \pm 990 \mathrm{μs}\left({\color{gray}4.96 \mathrm{\%}}\right) $$ Flame Graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

3 participants