Skip to content

H-4880, H-4881: Introduce Value type #7499

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

Merged
merged 24 commits into from
Jun 27, 2025
Merged

Conversation

indietyp
Copy link
Member

🌟 What is the purpose of this PR?

The Value type is used during evaluation for any value in HashQL, due to the constraints of the language it's immutable and uses structural sharing where possible.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

@indietyp indietyp requested review from hashdotai and Copilot June 26, 2025 15:30
@github-actions github-actions bot added area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team labels Jun 26, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new, immutable, and structurally shared Value type for HashQL and expands support for composite types (tuple, struct, list, dict, opaque) along with updated literal APIs.

  • Implements the Value enum with variant-specific behaviors and corresponding error types.
  • Adds new modules for tuple, struct, opaque, list, and dict while updating literal types and build configurations.

Reviewed Changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

File Description
libs/@local/hashql/core/src/value/*.rs New modules implementing the Value type variants and field/index access APIs.
libs/@local/hashql/core/src/literal/* Enhancements to literal types (string, integer, float) with additional conversion methods.
Cargo.toml & package.json Updates to dependencies and enabled features required by the new implementations.
Comments suppressed due to low confidence (1)

libs/@local/hashql/core/src/literal/float.rs:99

  • Consider adding an explicit 'use memchr::memchr3;' import at the top of the file to clearly indicate the dependency and improve code clarity for future maintainers.
        let is_integer = memchr::memchr3(b'.', b'e', b'E', self.value.as_bytes()).is_none();

@indietyp
Copy link
Member Author

@CodiumAI-Agent /review

Copy link
Member

@hashdotai hashdotai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a well-designed Value type system for HashQL that properly handles all the different types of values in the language. The implementation is very thorough, with each value type having appropriate operations and error handling.

The code is of high quality, with particularly strong points including:

  1. Immutability and Structural Sharing: All types are properly immutable, using structural sharing (via Rc and persistent data structures from the rpds crate) to maintain performance while providing immutable semantics.

  2. Comprehensive Error Handling: The error types and messages are clear and provide good context for debugging.

  3. Documentation: Every type and method has detailed documentation with examples, which is excellent for maintainability.

  4. Test Coverage: You've included property-based tests for complex algorithms like co_sort, which provides strong confidence in the correctness of the implementation.

  5. Performance Considerations: The implementation shows attention to performance details, like using memchr for parsing and efficient data structures.

I've left some minor comments in the diff, mostly noting good practices or suggesting minor improvements, but none of these are blockers. The code is well-structured, follows Rust best practices, and implements all the required functionality according to the Linear ticket.

Copy link

codecov bot commented Jun 26, 2025

Codecov Report

Attention: Patch coverage is 85.46366% with 58 lines in your changes missing coverage. Please review.

Project coverage is 53.22%. Comparing base (44b706a) to head (e06508c).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
libs/@local/codec/src/numeric/real.rs 73.95% 19 Missing and 6 partials ⚠️
libs/@local/hashql/core/src/value/mod.rs 81.70% 14 Missing and 1 partial ⚠️
libs/@local/hashql/core/src/value/dict.rs 86.48% 5 Missing ⚠️
libs/@local/hashql/core/src/value/list.rs 85.71% 5 Missing ⚠️
libs/@local/hashql/core/src/value/struct.rs 96.15% 1 Missing and 2 partials ⚠️
libs/@local/hashql/core/src/literal/integer.rs 81.81% 2 Missing ⚠️
libs/@local/hashql/core/src/value/tuple.rs 94.73% 2 Missing ⚠️
libs/@local/graph/store/src/filter/parameter.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7499      +/-   ##
==========================================
+ Coverage   53.08%   53.22%   +0.13%     
==========================================
  Files        1079     1085       +6     
  Lines       95645    96167     +522     
  Branches     4431     4447      +16     
==========================================
+ Hits        50774    51181     +407     
- Misses      43919    44020     +101     
- Partials      952      966      +14     
Flag Coverage Δ
apps.hash-ai-worker-ts 1.32% <ø> (ø)
apps.hash-api 0.00% <ø> (ø)
local.hash-backend-utils 3.68% <ø> (ø)
local.hash-isomorphic-utils 0.00% <ø> (ø)
rust.harpc-net 95.86% <ø> (ø)
rust.hash-codec 75.08% <73.95%> (-0.30%) ⬇️
rust.hash-graph-postgres-store 20.03% <ø> (-0.02%) ⬇️
rust.hash-graph-store 36.87% <0.00%> (ø)
rust.hash-graph-temporal-versioning 48.22% <ø> (ø)
rust.hash-graph-types 0.00% <ø> (ø)
rust.hash-graph-validation 83.30% <ø> (ø)
rust.hashql-ast 86.12% <ø> (ø)
rust.hashql-core 82.26% <89.40%> (+0.05%) ⬆️
rust.hashql-syntax-jexpr 94.10% <ø> (ø)

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.

@CodiumAI-Agent
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Index Error

In access_by_index for lists, InvalidListIndexType is constructed with the container’s type name instead of the index’s type name, which can mislead error handling.

pub fn access_by_index(&self, index: &Self) -> Result<Option<&Self>, IndexAccessError> {
    match self {
        Value::List(list) => {
            let integer = match index {
                &Self::Primitive(LiteralKind::Integer(integer)) => integer,
                Self::Primitive(LiteralKind::Float(float))
                    if let Some(integer) = float.as_integer() =>
                {
                    integer
                }
                _ => return Err(IndexAccessError::InvalidListIndexType(self.type_name())),
            };
Unused Import

The import of ops from core is not used and should be removed to avoid warnings.

use core::{fmt, ops, str::FromStr};

@indietyp
Copy link
Member Author

@CodiumAI-Agent /improve

@CodiumAI-Agent
Copy link

PR Code Suggestions ✨

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix float pattern match

The float arm never matches because index is a &Self, not a Self. Update the pattern
to &Self::Primitive so the branch can be taken and as_integer applied.

libs/@local/hashql/core/src/value/mod.rs [266-270]

-Self::Primitive(LiteralKind::Float(float))
-    if let Some(integer) = float.as_integer() =>
-{
-    integer
-}
+&Self::Primitive(LiteralKind::Float(float)) if let Some(integer) = float.as_integer() => integer,
Suggestion importance[1-10]: 9

__

Why: The float arm never matches because the pattern lacks &Self::Primitive, causing float.as_integer() never to be applied; this fixes a critical logic bug.

High
General
Report correct index type

The error reports the container's type (self.type_name()) instead of the invalid
index's type. Use index.type_name() to show the actual index type in the error.

libs/@local/hashql/core/src/value/mod.rs [271]

-_ => return Err(IndexAccessError::InvalidListIndexType(self.type_name())),
+_ => return Err(IndexAccessError::InvalidListIndexType(index.type_name())),
Suggestion importance[1-10]: 6

__

Why: Using index.type_name() instead of self.type_name() provides accurate error context for invalid index types, improving diagnostics.

Low

@indietyp
Copy link
Member Author

@CodiumAI-Agent /describe

@CodiumAI-Agent
Copy link

CodiumAI-Agent commented Jun 26, 2025

Title

(Describe updated until commit e06508c)

H-4880, H-4881: Introduce Value type


User description

🌟 What is the purpose of this PR?

The Value type is used during evaluation for any value in HashQL, due to the constraints of the language it's immutable and uses structural sharing where possible.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

PR Type

Enhancement, Tests


Description

  • Introduce Value enum and collection types

  • Extend literal conversions and ordering derives

  • Enhance Real with exact and lossy conversions

  • Update docs and dependency diagrams


Changes diagram

flowchart LR
  IL["`IntegerLiteral` conversions"]
  FL["`FloatLiteral` conversions"]
  R["`Real` numeric conversions"]
  V["`Value` enum & collections"]
  IL -- "typed conversions" --> V
  FL -- "integer/real conversions" --> R
  R -- "exact/lossy float" --> V
Loading

Changes walkthrough 📝

Relevant files
Enhancement
11 files
integer.rs
Add integer typed conversion methods                                         
+376/-87
real.rs
Enhance `Real` with exact and lossy conversions                   
+177/-21
struct.rs
Implement `Struct` value type with co_sort                             
+405/-0 
dict.rs
Implement `Dict` persistent map type                                         
+327/-0 
mod.rs
Add `Value` enum and access methods                                           
+284/-0 
list.rs
Implement `List` persistent sequence type                               
+288/-0 
float.rs
Add `FloatLiteral` integer and real conversions                   
+125/-50
tuple.rs
Implement `Tuple` type with symbol indexing                           
+226/-0 
opaque.rs
Implement `Opaque` nominal value type                                       
+105/-0 
string.rs
Add `as_bytes` and derive ordering                                             
+33/-1   
mod.rs
Derive ordering on `LiteralKind`                                                 
+1/-1     
Configuration changes
1 files
lib.rs
Enable features and expose `value` module                               
+4/-0     
Bug fix
1 files
parameter.rs
Use lossy float in filter parameters                                         
+1/-1     
Documentation
5 files
dependency-diagram.mmd
Update codec dependency diagram nodes                                       
+22/-9   
dependency-diagram.mmd
Update wire-protocol dependency diagram                                   
+22/-9   
dependency-diagram.mmd
Update codegen dependency diagram nodes                                   
+19/-7   
dependency-diagram.mmd
Update types dependency diagram nodes                                       
+19/-7   
dependency-diagram.mmd
Update HashQL core dependency diagram                                       
+25/-15 
Dependencies
2 files
package.json
Add library dependencies for numeric and collections         
+2/-0     
Cargo.toml
Add `hash-codec`, `memchr`, `rpds` dependencies                   
+1/-0     
Additional files
5 files
dependency-diagram.mmd +25/-15 
dependency-diagram.mmd +25/-15 
Cargo.toml +7/-2     
dependency-diagram.mmd +25/-15 
dependency-diagram.mmd +25/-15 

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @indietyp indietyp changed the title H-4881: Introduce Value type H-4880, H-4881: Introduce Value type Jun 26, 2025
    @indietyp indietyp force-pushed the bm/h-4881-hashql-introduce-value branch from 6db3ab9 to 3fa4c37 Compare June 26, 2025 17:09
    @indietyp indietyp force-pushed the bm/h-4879-make-the-postgres-module-public-in-graph-postgres-store branch from 5786759 to 7efbb3e Compare June 26, 2025 19:21
    @indietyp indietyp requested a review from a team as a code owner June 26, 2025 19:21
    @indietyp indietyp force-pushed the bm/h-4881-hashql-introduce-value branch from 638fe3f to 1c5e38e Compare June 26, 2025 19:21
    @indietyp indietyp force-pushed the bm/h-4879-make-the-postgres-module-public-in-graph-postgres-store branch from 7efbb3e to ae9f9af Compare June 26, 2025 20:34
    @indietyp indietyp force-pushed the bm/h-4881-hashql-introduce-value branch from 10d0a65 to c519933 Compare June 26, 2025 20:34
    Base automatically changed from bm/h-4879-make-the-postgres-module-public-in-graph-postgres-store to main June 26, 2025 21:14
    @indietyp indietyp force-pushed the bm/h-4881-hashql-introduce-value branch from c519933 to 223f535 Compare June 26, 2025 21:41
    @indietyp indietyp force-pushed the bm/h-4881-hashql-introduce-value branch from 223f535 to f570ad7 Compare June 27, 2025 15:28
    @indietyp indietyp removed the request for review from a team June 27, 2025 15:36
    TimDiekmann
    TimDiekmann previously approved these changes Jun 27, 2025
    Copy link
    Contributor

    Benchmark results

    @rust/hash-graph-benches – Integrations

    scaling_read_entity_linkless

    Function Value Mean Flame graphs
    entity_by_id 1 entities $$15.7 \mathrm{ms} \pm 83.9 \mathrm{μs}\left({\color{gray}1.60 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 10 entities $$15.5 \mathrm{ms} \pm 82.3 \mathrm{μs}\left({\color{gray}0.298 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 10000 entities $$27.5 \mathrm{ms} \pm 170 \mathrm{μs}\left({\color{gray}0.583 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 100 entities $$14.6 \mathrm{ms} \pm 79.0 \mathrm{μs}\left({\color{gray}-0.963 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 1000 entities $$15.1 \mathrm{ms} \pm 55.3 \mathrm{μs}\left({\color{lightgreen}-7.455 \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 $$5.92 \mathrm{ms} \pm 20.1 \mathrm{μs}\left({\color{gray}-1.316 \mathrm{\%}}\right) $$ Flame Graph

    representative_read_multiple_entities

    Function Value Mean Flame graphs
    entity_by_property depths: DT=0, PT=2, ET=2, E=2 $$116 \mathrm{ms} \pm 813 \mathrm{μs}\left({\color{gray}0.010 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_property depths: DT=0, PT=0, ET=2, E=2 $$109 \mathrm{ms} \pm 654 \mathrm{μs}\left({\color{gray}0.880 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_property depths: DT=0, PT=0, ET=0, E=2 $$104 \mathrm{ms} \pm 718 \mathrm{μs}\left({\color{gray}-0.291 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_property depths: DT=0, PT=0, ET=0, E=0 $$93.2 \mathrm{ms} \pm 782 \mathrm{μs}\left({\color{gray}-1.253 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_property depths: DT=255, PT=255, ET=255, E=255 $$130 \mathrm{ms} \pm 592 \mathrm{μs}\left({\color{gray}0.209 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_property depths: DT=2, PT=2, ET=2, E=2 $$119 \mathrm{ms} \pm 970 \mathrm{μs}\left({\color{gray}0.681 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=0, PT=2, ET=2, E=2 $$207 \mathrm{ms} \pm 1.01 \mathrm{ms}\left({\color{gray}-0.364 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=0, PT=0, ET=2, E=2 $$202 \mathrm{ms} \pm 854 \mathrm{μs}\left({\color{gray}-0.554 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=0, PT=0, ET=0, E=2 $$192 \mathrm{ms} \pm 1.12 \mathrm{ms}\left({\color{gray}-1.054 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=0, PT=0, ET=0, E=0 $$143 \mathrm{ms} \pm 1.01 \mathrm{ms}\left({\color{gray}-0.520 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=255, PT=255, ET=255, E=255 $$225 \mathrm{ms} \pm 1.27 \mathrm{ms}\left({\color{gray}0.705 \mathrm{\%}}\right) $$ Flame Graph
    link_by_source_by_property depths: DT=2, PT=2, ET=2, E=2 $$213 \mathrm{ms} \pm 1.65 \mathrm{ms}\left({\color{gray}0.100 \mathrm{\%}}\right) $$ Flame Graph

    scaling_read_entity_complete_one_depth

    Function Value Mean Flame graphs
    entity_by_id 50 entities $$5.56 \mathrm{s} \pm 546 \mathrm{ms}\left({\color{gray}-0.075 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 1 entities $$71.7 \mathrm{ms} \pm 308 \mathrm{μs}\left({\color{gray}-0.831 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 10 entities $$117 \mathrm{ms} \pm 383 \mathrm{μs}\left({\color{red}28.2 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 5 entities $$83.1 \mathrm{ms} \pm 383 \mathrm{μs}\left({\color{gray}-4.104 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 25 entities $$253 \mathrm{ms} \pm 1.27 \mathrm{ms}\left({\color{red}62.5 \mathrm{\%}}\right) $$ Flame Graph

    scaling_read_entity_complete_zero_depth

    Function Value Mean Flame graphs
    entity_by_id 50 entities $$17.4 \mathrm{ms} \pm 107 \mathrm{μs}\left({\color{lightgreen}-5.863 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 1 entities $$15.6 \mathrm{ms} \pm 43.1 \mathrm{μs}\left({\color{gray}0.645 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 10 entities $$15.3 \mathrm{ms} \pm 20.8 \mathrm{μs}\left({\color{gray}-0.486 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 5 entities $$14.8 \mathrm{ms} \pm 81.9 \mathrm{μs}\left({\color{gray}-0.592 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id 25 entities $$17.2 \mathrm{ms} \pm 79.5 \mathrm{μs}\left({\color{gray}0.240 \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/song/v/1 $$28.6 \mathrm{ms} \pm 237 \mathrm{μs}\left({\color{lightgreen}-6.156 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$29.1 \mathrm{ms} \pm 278 \mathrm{μs}\left({\color{lightgreen}-12.335 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$29.2 \mathrm{ms} \pm 279 \mathrm{μs}\left({\color{gray}-1.673 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$30.7 \mathrm{ms} \pm 234 \mathrm{μs}\left({\color{gray}4.90 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$29.7 \mathrm{ms} \pm 253 \mathrm{μs}\left({\color{gray}3.72 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$29.2 \mathrm{ms} \pm 243 \mathrm{μs}\left({\color{gray}-0.734 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$30.3 \mathrm{ms} \pm 263 \mathrm{μs}\left({\color{gray}2.71 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$29.7 \mathrm{ms} \pm 261 \mathrm{μs}\left({\color{gray}2.63 \mathrm{\%}}\right) $$ Flame Graph
    entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$28.9 \mathrm{ms} \pm 233 \mathrm{μs}\left({\color{gray}-2.564 \mathrm{\%}}\right) $$ Flame Graph

    Comment on lines +621 to +623
    let inside_f32 = Real::from_natural(123_456, 0); // 123456 - needs ~17 bits
    let inside_f64 = Real::from_natural(16_777_217, 0); // 2^24 + 1 - needs 25 bits
    let outside_f64 = Real::from_natural(123_456_789, 20); // 123456789 * 10^20
    Copy link
    Member

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Can't we just pass the exponent? e.g. from_natural(1, 17)? That's more intuitive.

    Copy link
    Member Author

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    in theory yes, but the issue described only happens if the significand is larger than eg 53 bits, which wouldn't happen if we use an exponent because the exponent is saved separately

    @indietyp indietyp added this pull request to the merge queue Jun 27, 2025
    Merged via the queue into main with commit 9fd37f2 Jun 27, 2025
    162 checks passed
    @indietyp indietyp deleted the bm/h-4881-hashql-introduce-value branch June 27, 2025 23:52
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    area/deps Relates to third-party dependencies (area) 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.

    4 participants