Skip to content

Conversation

@martin-georgiev
Copy link
Owner

@martin-georgiev martin-georgiev commented Apr 12, 2025

Summary by CodeRabbit

  • New Features
    • Introduced comprehensive support for advanced PostgreSQL JSONB queries in the ORM, enabling checks for JSON path existence, matching conditions, and retrieving JSON items (as arrays or single entries).
    • Enhanced validation for boolean parameters with clearer error messaging, ensuring more robust and predictable handling of JSON queries.

…SONB_PATH_QUERY()`, `JSONB_PATH_QUERY_ARRAY()` and `JSONB_PATH_QUERY_FIRST()`
@coveralls
Copy link

Coverage Status

coverage: 94.808% (-0.3%) from 95.11%
when pulling d96dbda on new-json-funcs
into c3cb08d on main.

@coderabbitai
Copy link

coderabbitai bot commented Apr 12, 2025

Walkthrough

This update introduces a new exception class for validating boolean parameters and adds several new JSONB function classes for Doctrine ORM. Each JSONB function class implements a specific PostgreSQL JSONB operation by extending a base variadic class and using a shared boolean validation trait. Additionally, corresponding test cases have been added to ensure proper argument checks and error handling across all new JSONB functionalities.

Changes

File(s) Change Summary
src/.../Exception/InvalidBooleanException.php Added InvalidBooleanException with static methods forNonLiteralNode and forInvalidBoolean for generating specific error messages.
src/.../Functions/JsonbPathExists.php, JsonbPathMatch.php, JsonbPathQuery.php, JsonbPathQueryArray.php, JsonbPathQueryFirst.php Added new classes implementing PostgreSQL JSONB functions via BaseVariadicFunction and BooleanValidationTrait, with customized prototypes and argument validations.
src/.../Functions/Traits/BooleanValidationTrait.php Added BooleanValidationTrait for validating boolean parameters, including methods to check and enforce valid boolean values.
tests/.../Functions/JsonbPathExistsTest.php, JsonbPathMatchTest.php, JsonbPathQueryArrayTest.php, JsonbPathQueryFirstTest.php, JsonbPathQueryTest.php Added test classes to validate functionality and error handling for the new JSONB functions, ensuring correct SQL/DQL generation and proper exception throwing on invalid inputs.

Sequence Diagram(s)

sequenceDiagram
    participant U as DQL Query
    participant F as JSONB Function
    participant V as BooleanValidationTrait
    participant E as Exception

    U ->> F: Invoke JSONB function with arguments
    F ->> F: Customize function prototype
    F ->> F: Validate argument count
    alt Four arguments provided
        F ->> V: Validate fourth argument as boolean
        V -->> F: Return validation result
        alt Validation fails
            F ->> E: Throw InvalidBooleanException
        end
    end
    F -->> U: Return formatted SQL function call
Loading

Possibly related PRs

Poem

Oh hop along, code on the run,
A new exception has now begun.
JSONB functions prance with grace,
Validating booleans in every case.
With tests to guide each careful leap,
I, the rabbit, watch secrets keep! 🐇✨

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQuery.php (1)

33-44: Argument validation logic is robust.
The enforced range of 2 to 4 arguments for jsonb_path_query and optional boolean validation for the fourth argument matches PostgreSQL's function specification. However, this validation block is nearly identical across your new classes; consider centralizing repeated logic (like min/max argument checks and optional boolean checks) in a shared helper or trait if you anticipate future expansions or modifications.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExists.php (1)

32-43: Repeatable argument validation.
You enforce a 2–4 argument range plus an optional boolean check on the 4th argument. This is identical or similar to logic present in the other new JSONB classes. If future changes to argument validation are necessary, you may find it beneficial to consolidate this logic into a single shared method or trait.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirst.php (1)

33-44: Consistent argument count and boolean parameter checks.
Verifying that arguments fall between 2 and 4, and ensuring the optional fourth argument is a valid boolean aligns with prior classes. Since the same logic appears across your JSONB functions, you might consider a single closure or trait for common argument validation steps.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatch.php (1)

34-44: Argument validation approach mirrors the other JSONB utilities.
Like the related classes, you enforce a 2–4 range of arguments and validate the fourth as a boolean. If you foresee more JSONB path functions, centralizing repetitive validation steps in a dedicated utility or trait could reduce duplication.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)

25-37: Consider simplifying double-lowercasing.

You call \strtolower() twice for the $value (line 31 and again line 32). Eliminating one step can reduce minor redundancy:

- $value = \strtolower(\trim((string) $node->value, "'\""));
- $lowercaseValue = \strtolower($value);
+ $value = \strtolower(\trim((string) $node->value, "'\""));
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArrayTest.php (1)

50-57:

❓ Verification inconclusive

Consider adding a test for NULL values in JSON path expressions.

While the current tests cover most scenarios, PostgreSQL's documentation mentions specific behavior for NULL handling in JSON path expressions. Consider adding a test case that verifies the behavior when querying for NULL values or when the path doesn't exist.

Also applies to: 59-66


🌐 Web query:

How does PostgreSQL's jsonb_path_query_array handle NULL values or non-existent paths?

💡 Result:

PostgreSQL's jsonb_path_query_array function handles NULL values and non-existent paths with specific behaviors informed by JSONPath evaluation modes (lax vs. strict) and input validity:

  1. NULL Input Handling:

    • Returns NULL if either the input JSONB data or the JSON path is NULL[1][9].
    • Explicit JSON null values in the data are treated as valid elements unless filtered out by the path expression[6].
  2. Non-Existent Paths:

    • Lax Mode (default): Returns an empty array [] if no elements match the path, including cases where the path structure doesn't exist[1][4][5]. For example:
      SELECT jsonb_path_query_array('{"a": 1}', '$.b'); -- Returns []
    • Strict Mode: Triggers an error if the path structure doesn't match the JSONB data[8]. For example:
      SELECT jsonb_path_query_array('{"a": 1}', 'strict $.b'); -- Error: path does not exist
  3. JSON null vs. Missing Keys:

    • JSON null values within arrays are included in results unless explicitly filtered:
      SELECT jsonb_path_query_array('[5, null, 9]', '$[*]'); -- Returns [5, null, 9]
    • Use filters to exclude JSON null values:
      SELECT jsonb_path_query_array('[5, null, 9]', '$[*] ? (@ != null)'); -- Returns [5, 9][2][5]
  4. Comparison with Related Functions:

    • Unlike jsonb_path_query_first (returns NULL for no matches), jsonb_path_query_array always returns an array, even if empty[9].
    • Contrasts with jsonb_strip_nulls, which removes object fields with null values but leaves array nulls intact unless using PostgreSQL 18's new strip_in_arrays parameter[2].

For precise control, combine path filters (e.g., ? (@ != null)) and mode specifiers (lax/strict) to manage NULLs and path existence checks.

Citations:


Update Suggested: Expand Test Coverage for NULL and Missing JSON Path Scenarios

The current tests effectively handle argument count errors, but PostgreSQL’s jsonb_path_query_array has well-defined behaviors regarding NULL inputs and non-existent paths. In addition to existing tests, please consider:

  • Testing NULL Handling:
    Verify that if either the JSON data or the JSON path is NULL, the function returns NULL.

  • Testing Missing Paths:
    In the default lax mode, ensure that a non-existent path returns an empty array ([]). Optionally, add a test using strict mode to confirm that a missing path triggers an error.

These enhancements should be applied where relevant (e.g., lines 50–57 and 59–66) to further align the test suite with PostgreSQL's documented behavior.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirstTest.php (1)

41-48: Consider adding a more complex path expression test.

While the current tests cover standard usage patterns, adding a test for a more complex path expression that demonstrates recursive descent (**) or multiple filters would provide more comprehensive coverage according to PostgreSQL's jsonpath documentation.

+ 'extracts with recursive descent' => "SELECT jsonb_path_query_first(c0_.object1, '$.**[*] ? (@.type == \"special\")') AS sclr_0 FROM ContainsJsons c0_",

And in getDqlStatements():

+ 'extracts with recursive descent' => \sprintf("SELECT JSONB_PATH_QUERY_FIRST(e.object1, '$.**[*] ? (@.type == \"special\")') FROM %s e", ContainsJsons::class),
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryTest.php (1)

21-29:

❓ Verification inconclusive

Consider adding more complex JSONPath examples

The test cases cover the basic parameter combinations for jsonb_path_query, but could be more comprehensive by including more complex JSONPath expressions from PostgreSQL documentation.

According to PostgreSQL docs, jsonb_path_query supports advanced features like recursive descent (**), array slicing, multiple filter conditions, etc.

    protected function getExpectedSqlStatements(): array
    {
        return [
            'extracts items with condition' => "SELECT jsonb_path_query(c0_.object1, '$.items[*] ? (@.price > 100)') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path with vars argument' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path with vars and silent arguments' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
+           'recursive descent example' => "SELECT jsonb_path_query(c0_.object1, '$.**') AS sclr_0 FROM ContainsJsons c0_",
+           'array slicing example' => "SELECT jsonb_path_query(c0_.object1, '$.items[0 to 2]') AS sclr_0 FROM ContainsJsons c0_",
+           'multiple conditions example' => "SELECT jsonb_path_query(c0_.object1, '$.items[*] ? (@.price > 10 && @.category == \"electronics\")') AS sclr_0 FROM ContainsJsons c0_",
        ];
    }

🌐 Web query:

PostgreSQL jsonb_path_query examples and syntax

💡 Result:

PostgreSQL's jsonb_path_query() function enables advanced querying of JSONB data using SQL/JSON path expressions. Here's a structured overview of its capabilities and usage:

Basic Syntax

jsonb_path_query(target JSONB, path VARCHAR [, vars JSONB [, silent BOOLEAN]])
  • target: JSONB data to query
  • path: JSON path expression
  • vars: Variables for path expressions (optional)
  • silent: Suppress errors (default: false)

Returns a set of JSONB values matching the path[1][2][4].


Key Features and Examples

1. Simple Value Extraction

SELECT jsonb_path_query('{"name": "Alice", "age": 30}', '$.name');
-- Result: "Alice" [2][4]

2. Array Traversal

SELECT jsonb_path_query(
    '{"employees": [{"name": "John"}, {"name": "Jane"}]}',
    '$.employees[*].name'
);
-- Results: "John", "Jane" [1][7]

3. Filter Expressions

SELECT jsonb_path_query(
    '[1,2,3,4,5]',
    '$[*] ? (@ > 3)'
);
-- Results: 4, 5 [2][6]

4. Nested Object Querying

SELECT jsonb_path_query(
    '{"product": {"specs": {"cpu": "i7", "ram": "16GB"}}}',
    '$.product.specs.cpu'
);
-- Result: "i7" [4][8]

5. Variable Usage

SELECT jsonb_path_query(
    '[10,20,30,40]',
    '$[*] ? (@ >= $min && @ <= $max)',
    '{"min": 15, "max": 35}'
);
-- Results: 20, 30 [2][6]

Comparison with Related Functions

Function Returns Use Case
jsonb_path_query() Set of JSONB Multiple matches
jsonb_path_query_first() Single JSONB First match
jsonb_path_query_array() JSONB array Array-formatted results

Critical Considerations

  1. Path Modes

    • Lax (default): Automatically unwraps arrays
    SELECT jsonb_path_query('{"a": [1]}', 'lax $.a');
    -- Result: 1
    • Strict: Requires explicit array handling
    SELECT jsonb_path_query('{"a": [1]}', 'strict $.a');
    -- Result: [1] [9]
  2. Error Handling

    SELECT jsonb_path_query('{}', 'strict $.missing', '{}', true);
    -- Returns empty instead of error with silent=true [10]
  3. Performance Tips

    • Use jsonb_path_ops GIN indexes for path queries
    • Prefer @? and @@ operators for existence checks[8][10]

For complex JSON processing, combine with other JSONB functions like jsonb_each or jsonb_array_elements to handle nested structures efficiently[6][8].

Citations:


Enhance Test Coverage with Advanced JSONPath Scenarios

The current test cases successfully cover basic usages of jsonb_path_query(). However, PostgreSQL’s documentation confirms support for more advanced JSONPath constructs—including recursive descent, array slicing, and complex filter expressions. Expanding the tests to cover these features would improve confidence in handling intricate JSONB queries.

Proposed additions:

  • Recursive Descent: Verify deep JSON inspection using a path like $.**.
  • Array Slicing: Test proper slice evaluation by using an expression such as $.items[0 to 2].
  • Combined Conditions: Ensure multiple filter conditions are processed correctly with an expression like $.items[*] ? (@.price > 10 && @.category == "electronics").
    protected function getExpectedSqlStatements(): array
    {
        return [
            'extracts items with condition' => "SELECT jsonb_path_query(c0_.object1, '$.items[*] ? (@.price > 100)') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path with vars argument' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
            'extracts items from path with vars and silent arguments' => "SELECT jsonb_path_query(c0_.object1, '$.items[*].id', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
+           'recursive descent example' => "SELECT jsonb_path_query(c0_.object1, '$.**') AS sclr_0 FROM ContainsJsons c0_",
+           'array slicing example' => "SELECT jsonb_path_query(c0_.object1, '$.items[0 to 2]') AS sclr_0 FROM ContainsJsons c0_",
+           'multiple conditions example' => "SELECT jsonb_path_query(c0_.object1, '$.items[*] ? (@.price > 10 && @.category == \"electronics\")') AS sclr_0 FROM ContainsJsons c0_",
        ];
    }

Please verify that these examples align with your intended test coverage and accurately reflect PostgreSQL’s JSONB querying capabilities.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3cb08d and d96dbda.

📒 Files selected for processing (12)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExists.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatch.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQuery.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArray.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirst.php (1 hunks)
  • src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExistsTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatchTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArrayTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirstTest.php (1 hunks)
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php`: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL que...

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/**/*.php: Use the PostgreSQL official documentation to verify that tests include comprehensive use cases and example SQL queries for the tested SQL functions and operators.

  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExistsTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirstTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArrayTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatchTest.php
  • tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryTest.php
🧬 Code Graph Analysis (10)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExists.php (3)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
  • validateBoolean (25-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatch.php (3)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
  • validateBoolean (25-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQuery.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php (1)
  • BaseVariadicFunction (20-73)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
  • validateBoolean (25-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirst.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php (1)
  • BaseVariadicFunction (20-73)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
  • validateBoolean (25-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArray.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (2)
  • InvalidArgumentForVariadicFunctionException (7-46)
  • between (29-37)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php (1)
  • BaseVariadicFunction (20-73)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseFunction.php (1)
  • setFunctionPrototype (57-60)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
  • validateBoolean (25-37)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirstTest.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (1)
  • InvalidBooleanException (14-33)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirst.php (1)
  • JsonbPathQueryFirst (24-45)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (2)
  • TestCase (15-120)
  • buildEntityManager (116-119)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (3)
  • InvalidBooleanException (14-33)
  • forNonLiteralNode (16-23)
  • forInvalidBoolean (25-32)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArrayTest.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (1)
  • InvalidBooleanException (14-33)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArray.php (1)
  • JsonbPathQueryArray (23-44)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (2)
  • TestCase (15-120)
  • buildEntityManager (116-119)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatchTest.php (4)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (1)
  • InvalidBooleanException (14-33)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatch.php (1)
  • JsonbPathMatch (25-46)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TestCase.php (2)
  • TestCase (15-120)
  • buildEntityManager (116-119)
tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryTest.php (3)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidArgumentForVariadicFunctionException.php (1)
  • InvalidArgumentForVariadicFunctionException (7-46)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (1)
  • InvalidBooleanException (14-33)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQuery.php (1)
  • JsonbPathQuery (24-45)
⏰ Context from checks skipped due to timeout of 90000ms (20)
  • GitHub Check: PHP 8.3 + Doctrine ORM latest + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.1 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.18 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.14 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer latest
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 1.2
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer latest
  • GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer latest
  • GitHub Check: PHP 8.3 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer latest
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.14 + Doctrine Lexer 2.1
  • GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer 3.0
  • GitHub Check: PHP 8.3 + Doctrine ORM 2.18 + Doctrine Lexer 3.0
  • GitHub Check: sloth
🔇 Additional comments (31)
src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQuery.php (2)

11-23: Comprehensive docblock structure.
Your docblock is thorough, including relevant references, a usage example, and versioning, which helps future maintainers.


28-31: Function prototype correctly set.
Using jsonb_path_query(%s) in customizeFunction() is consistent with PostgreSQL's function signature and Doctrine's typical approach to building SQL strings.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExists.php (2)

11-23: Docblock clarity and completeness.
You have effectively documented the function’s behavior, references, and example usage, making it easy for others to understand the purpose of jsonb_path_exists.


27-30: Function prototype matches PostgreSQL syntax.
Setting jsonb_path_exists(%s) ensures generated SQL correlates with PostgreSQL's native JSONB_PATH_EXISTS() function. Good job maintaining consistent naming and style.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirst.php (2)

11-23: Well-structured documentation.
The docblock clarifies usage, references, and examples for JSONB_PATH_QUERY_FIRST(). The guidance on expected return values (NULL if no results) is especially helpful.


28-31: Accurate function prototype.
Using jsonb_path_query_first(%s) ensures the function aligns with PostgreSQL’s syntax. This is consistent with the approach used in your other JSONB classes.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatch.php (2)

11-23: Thorough documentation and examples.
Providing an example of a JSON path predicate clarifies how to use jsonb_path_match in real scenarios. Including the caveat that non-boolean results will fail or return NULL is especially helpful.


29-32: Prototype reflects correct PostgreSQL function name.
Defining jsonb_path_match(%s) aligns with the database function’s specification. This consistency helps keep your library intuitive for users.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArray.php (3)

23-26: Good use of an extended variadic function class.

Declaring JsonbPathQueryArray to extend BaseVariadicFunction is a sound approach for reusing shared parsing logic. The class-level doc provides good clarity for its purpose and usage.


27-31: Function prototype setup looks correct.

Calling setFunctionPrototype('jsonb_path_query_array(%s)') aligns well with PostgreSQL’s syntax. No issues detected with the customizeFunction() method.


32-43: Argument validation is well-handled.

Enforcing 2 to 4 arguments and validating the optional fourth as boolean is clear and consistent with PostgreSQL usage. Ensure test coverage includes cases with 2, 3, and 4 arguments to confirm correct behavior.

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Exception/InvalidBooleanException.php (2)

14-23: Appropriate exception creation for non-literal nodes.

Method forNonLiteralNode precisely identifies and reports invalid node usage, providing actionable feedback to the user.


25-32: Clear error messaging for invalid boolean values.

Method forInvalidBoolean is straightforward, giving concise guidance on expected values ("true" or "false").

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Traits/BooleanValidationTrait.php (1)

39-42: Boolean check method is concise and efficient.

Using \in_array($boolean, ['true', 'false'], true) is a secure way to confirm accepted values without ambiguity.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryArrayTest.php (3)

21-29: Well-structured SQL test cases for JSONB_PATH_QUERY_ARRAY.

The SQL statements provide good coverage for testing the core functionality of jsonb_path_query_array, including array extraction with conditions, nested path extraction, and the use of optional arguments. This aligns with PostgreSQL's documented functionality.


31-39: DQL statements properly mirror the SQL test cases.

The DQL statements correctly match with the expected SQL statements, providing a clear test path from DQL to SQL transformation.


41-48: Boolean validation correctly implemented and tested.

This test properly verifies that the function throws an InvalidBooleanException when an invalid boolean value is provided, ensuring that the fourth parameter is properly validated when present.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathExistsTest.php (3)

21-29: Well-implemented SQL test cases for JSONB_PATH_EXISTS.

The test cases correctly demonstrate the various ways jsonb_path_exists can be used, including condition checks, nested path existence verification, and optional arguments usage. The coverage aligns with PostgreSQL's documentation.


31-39: DQL statements correctly match SQL expectations.

The DQL statements are properly formed and correspond to the expected SQL translations, ensuring that Doctrine correctly interprets the JSONB_PATH_EXISTS function calls.


41-48: Error handling tests are thorough.

The tests appropriately verify that exceptions are thrown for invalid input scenarios, ensuring robust error handling for the function. Boolean validation and argument count validation are both properly tested.

Also applies to: 50-57, 59-66

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryFirstTest.php (2)

21-29: Comprehensive test cases for JSONB_PATH_QUERY_FIRST function.

The SQL statements effectively test the jsonb_path_query_first function with conditions, path extractors, and optional arguments, covering the main functionality documented in PostgreSQL.


31-39: DQL statements properly formatted with entity references.

The DQL statements use proper formatting with the ContainsJsons entity class and maintain consistency with the expected SQL statements.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatchTest.php (3)

21-29: Good test coverage for JSONB_PATH_MATCH with different expressions.

The test cases effectively demonstrate the jsonb_path_match function with various path expressions, including exists conditions and direct boolean comparisons, which align with PostgreSQL's documentation.


31-39: DQL statements correctly mirror SQL expectations.

The DQL statements properly correspond to the expected SQL translations, ensuring Doctrine correctly handles the JSONB_PATH_MATCH function calls.


41-66: Error handling properly tested.

The tests for exception handling ensure that the function validates its arguments properly, throwing appropriate exceptions for invalid booleans and incorrect argument counts.

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathQueryTest.php (6)

1-13: Well-structured test class definition

The test class is properly structured with clear namespace, appropriate imports, and correct extension of TestCase. All necessary dependencies are imported correctly.


14-19: Function registration looks good

The getStringFunctions method correctly maps the 'JSONB_PATH_QUERY' function name to the JsonbPathQuery implementation class.


31-39: Matching DQL statements are well structured

The DQL statements correctly match the SQL statements and use proper formatting with the ContainsJsons entity class. If you add additional test cases to the SQL statements, remember to add corresponding DQL statements here.


41-48: Error handling test for invalid boolean is appropriate

This test properly verifies that an invalid boolean value throws the expected exception with the correct message.


50-57: Error handling test for argument count (too few) is appropriate

This test correctly verifies that providing too few arguments to the function throws the expected exception with the correct message.


59-66: Error handling test for argument count (too many) is appropriate

This test correctly verifies that providing too many arguments to the function throws the expected exception with the correct message.

Comment on lines +24 to +27
'checks if path matches with exists condition' => "SELECT jsonb_path_match(c0_.object1, 'exists($.a[*] ? (@ >= 2 && @ <= 4))') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars argument' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars and silent arguments' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add test case for numeric and array literals in path expressions.

PostgreSQL's documentation for jsonpath shows support for comparing with numeric literals and array literals. Consider adding test cases that demonstrate this functionality:

+ 'checks if path matches with numeric literal comparison' => "SELECT jsonb_path_match(c0_.object1, '$.value == 42') AS sclr_0 FROM ContainsJsons c0_",
+ 'checks if path matches with array literal comparison' => "SELECT jsonb_path_match(c0_.object1, '$.tags[*] == \"important\"') AS sclr_0 FROM ContainsJsons c0_",

With corresponding DQL statements:

+ 'checks if path matches with numeric literal comparison' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.value == 42') FROM %s e", ContainsJsons::class),
+ 'checks if path matches with array literal comparison' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.tags[*] == \"important\"') FROM %s e", ContainsJsons::class),

🌐 Web query:

PostgreSQL jsonpath numeric and array literal examples

💡 Result:

PostgreSQL's jsonpath provides a powerful way to query JSON data using SQL/JSON path language. Below are key examples and syntax rules for handling numeric values and arrays:


Numeric Literals

jsonpath supports numeric literals in JavaScript-style formats:

  • Integers: 42, -15
  • Decimals: 3.14, -.5 (equivalent to -0.5)
  • Scientific notation: 1e5 (100,000), 2.5E-3 (0.0025)

Filter example (retrieve players with score > 1,000,000):

SELECT jsonb_path_query(data, '$.players[*] ? (@.score > 1e6)')
FROM configs;

This returns players whose score field exceeds 1,000,000[1][3].


Array Literals and Access

Arrays use 0-based indexing and square bracket notation:

$.players[0].username  -- First player's username
$.players[-1]          -- Last array element (Mary84 in sample data)
$.players[0 to 2]      -- First three players

Multidimensional array example (access nested achievement lists):

SELECT jsonb_path_query(data, '$.players[*].achievements[0]')
FROM configs;

Returns the first achievement for each player ("First Victory" twice)[1][3].


Array Comparisons and Filters

Use [*] wildcard and comparison operators:

$.track.segments[*].location  -- All location coordinates[3]
$.players[*] ? (@.score > 50000 && @.score < 3e6)  -- Mid-range scores[2]

Strict vs. Lax mode:

-- Strict mode (errors on invalid path)
SELECT jsonb_path_query('[[1,2]]', 'strict $[0][2]'); -- ERROR

-- Lax mode (returns empty instead of error)
SELECT jsonb_path_query('[[1,2]]', 'lax $[0][2]'); -- Empty result[5]

Special Array Operations

Operation Example Description
size() method $.players.size() Returns array length (3)[3]
double() conversion $.floor.apt.area.double() Converts string to number[2]
Nested array access $..achievements[0,2] First and third achievements[3]

For empty arrays, use exists checks:

SELECT jsonb_path_exists(data, '$.empty_array.exists()'); -- false[5]

These examples demonstrate how jsonpath combines SQL-like filtering with JavaScript-inspired syntax for flexible JSON querying in PostgreSQL. Key differences from standard SQL include 0-based indexing and relaxed numeric literal handling[3][5].

Citations:


Add test cases for numeric and array literal comparisons in jsonpath expressions

Please add new test cases in the file tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbPathMatchTest.php to ensure that comparisons using numeric and array literals work as documented. According to PostgreSQL’s jsonpath documentation, numeric literals (e.g., 42) and array operations (using square bracket notation with wildcards) are fully supported. Consider augmenting your test suite with cases resembling the diff below:

+ 'checks if path matches with numeric literal comparison' => "SELECT jsonb_path_match(c0_.object1, '$.value == 42') AS sclr_0 FROM ContainsJsons c0_",
+ 'checks if path matches with array literal comparison'   => "SELECT jsonb_path_match(c0_.object1, '$.tags[*] == \"important\"') AS sclr_0 FROM ContainsJsons c0_",

And corresponding DQL statements:

+ 'checks if path matches with numeric literal comparison' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.value == 42') FROM %s e", ContainsJsons::class),
+ 'checks if path matches with array literal comparison'   => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.tags[*] == \"important\"') FROM %s e", ContainsJsons::class),

This update will help cover the functionality outlined in PostgreSQL's jsonpath documentation, ensuring that your DQL queries handle numeric and array literals as expected.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'checks if path matches with exists condition' => "SELECT jsonb_path_match(c0_.object1, 'exists($.a[*] ? (@ >= 2 && @ <= 4))') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars argument' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars and silent arguments' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with exists condition' => "SELECT jsonb_path_match(c0_.object1, 'exists($.a[*] ? (@ >= 2 && @ <= 4))') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars argument' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars and silent arguments' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
+ 'checks if path matches with numeric literal comparison' => "SELECT jsonb_path_match(c0_.object1, '$.value == 42') AS sclr_0 FROM ContainsJsons c0_",
+ 'checks if path matches with array literal comparison' => "SELECT jsonb_path_match(c0_.object1, '$.tags[*] == \"important\"') AS sclr_0 FROM ContainsJsons c0_",
Suggested change
'checks if path matches with exists condition' => "SELECT jsonb_path_match(c0_.object1, 'exists($.a[*] ? (@ >= 2 && @ <= 4))') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars argument' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with simple condition and vars and silent arguments' => "SELECT jsonb_path_match(c0_.object1, '$.a[*] > 2', '{\"strict\": false}', 'true') AS sclr_0 FROM ContainsJsons c0_",
'checks if path matches with exists condition' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, 'exists($.a[*] ? (@ >= 2 && @ <= 4))') FROM %s e", ContainsJsons::class),
'checks if path matches with simple condition' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.a[*] > 2') FROM %s e", ContainsJsons::class),
'checks if path matches with simple condition and vars argument' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.a[*] > 2', '{\"strict\": false}') FROM %s e", ContainsJsons::class),
'checks if path matches with simple condition and vars and silent arguments' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.a[*] > 2', '{\"strict\": false}', 'true') FROM %s e", ContainsJsons::class),
+ 'checks if path matches with numeric literal comparison' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.value == 42') FROM %s e", ContainsJsons::class),
+ 'checks if path matches with array literal comparison' => \sprintf("SELECT JSONB_PATH_MATCH(e.object1, '$.tags[*] == \"important\"') FROM %s e", ContainsJsons::class),

@martin-georgiev martin-georgiev merged commit 0cda902 into main Apr 13, 2025
48 of 50 checks passed
@martin-georgiev martin-georgiev deleted the new-json-funcs branch April 13, 2025 00:22
@github-actions github-actions bot mentioned this pull request Apr 12, 2025
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.

3 participants