Skip to content

feat: Port whereJsonContainsKey methods + CompilesJsonPaths from Laravel#7699

Merged
limingxinleo merged 14 commits intohyperf:masterfrom
binaryfire:feature/json-contains-key
Jan 25, 2026
Merged

feat: Port whereJsonContainsKey methods + CompilesJsonPaths from Laravel#7699
limingxinleo merged 14 commits intohyperf:masterfrom
binaryfire:feature/json-contains-key

Conversation

@binaryfire
Copy link
Contributor

@binaryfire binaryfire commented Jan 18, 2026

Summary

This PR adds 4 missing Query Builder methods for checking if a JSON path exists:

  • whereJsonContainsKey()
  • orWhereJsonContainsKey()
  • whereJsonDoesntContainKey()
  • orWhereJsonDoesntContainKey()

These methods exist in Laravel but were missing from Hyperf.

Changes

1. New Builder Methods

Added to src/database/src/Query/Builder.php:

$query->whereJsonContainsKey('options->languages');
$query->whereJsonDoesntContainKey('options->languages');

2. Grammar Support

Each database has different SQL syntax for this operation:

Database SQL Output
MySQL ifnull(json_contains_path(column, 'one', path), 0)
PostgreSQL coalesce((column)::jsonb ?? 'key', false)
SQLite json_type(column, path) is not null

3. CompilesJsonPaths Trait

Ported Laravel's new CompilesJsonPaths trait to share improved JSON path compilation logic between Query Grammar and Schema Grammar classes.

Why this was needed:

Hyperf's wrapJsonPath (base Grammar) and wrapJsonPathAttributes (Postgres) were outdated and didn't handle array indices in JSON paths. For example, options->languages[0][1] was incorrectly treated as a single quoted key "languages[0][1]" instead of being parsed as "languages"[0][1].

The fix required updating these methods, but they were duplicated across Query Grammar and SQLite Schema Grammar. Laravel solves this with a shared trait.

Changes:

  • Created CompilesJsonPaths trait in src/database/src/Concerns/ (matches Laravel's architecture)
  • Query Grammar and Schema Grammar both use the trait (eliminates code duplication)
  • Removed duplicate methods from SQLite Schema Grammar
  • PostgresGrammar.php: Updated wrapJsonPathAttributes() + added parseJsonPathArrayKeys()

The trait contains:

  • wrapJsonFieldAndPath() - splits JSON selector into field and path
  • wrapJsonPath() - wraps JSON path with proper array index handling
  • wrapJsonPathSegment() - parses individual path segments including array indices

Test fix:

The existing MySQL test in QueryBuilderTest.php had an incorrect expectation that matched the buggy behavior:

// Old (incorrect)
$this->assertSame('... \'$."languages[0][1]"\'...', $builder->toSql());

// New (correct - matches Laravel)
$this->assertSame('... \'$."languages"[0][1]\'...', $builder->toSql());

Laravel's test confirms the correct expectation: DatabaseQueryBuilderTest.php:6682-6683

4. PostgreSQL / SQLite Test Structure Fix

The database-pgsql and database-sqlite test files had two problems:

Problem 1: Class names

The old names were not consistent. I renamed them to match Laravel's class names:

Package Schema Builder Test Query Builder Test
database-pgsql (old) SchemaBuilderTest DatabasePostgresBuilderTest
database-pgsql (new) DatabasePostgresSchemaBuilderTest DatabasePostgresQueryBuilderTest
database-sqlite (old) DatabaseSQLiteBuilderTest DatabaseSQLiteQueryGrammarTest
database-sqlite (new) DatabaseSQLiteSchemaBuilderTest DatabaseSQLiteQueryBuilderTest

I also removed a duplicate test class.

Problem 2: Postgres tests used deprecated Swoole extension

The main ContainerStub and DatabasePostgresBuilderTest test files used the deprecated pgsql-swoole driver:

// Old - deprecated Swoole extension
'driver' => 'pgsql-swoole',
PostgresSqlSwooleExtConnector::class
PostgreSqlSwooleExtConnection::class

Hyperf now uses PDO for PostgreSQL. The tests only run on Swoole < 6.0.

Solution:

  1. Renamed old files to clearly show they test the deprecated extension:

    • ContainerStub.phpSwooleExtContainerStub.php
    • DatabasePostgresBuilderTest.phpDatabasePostgresSwooleExtQueryBuilderTest.php
  2. Created new files for PDO driver:

    • ContainerStub.php - uses pgsql driver with PDO
    • DatabasePostgresQueryBuilderTest.php - pure unit tests like Laravel (no database needed)

Files Changed

New trait + JSON path fixes:

  • src/database/src/Concerns/CompilesJsonPaths.php - new trait (matches Laravel's architecture)
  • src/database/src/Query/Grammars/Grammar.php - uses CompilesJsonPaths trait + new compile method
  • src/database/src/Schema/Grammars/Grammar.php - uses CompilesJsonPaths trait
  • src/database-pgsql/src/Query/Grammars/PostgresGrammar.php - new compile method + wrapJsonPathAttributes improvements
  • src/database-sqlite/src/Query/Grammars/SQLiteGrammar.php - new compile method
  • src/database-sqlite/src/Schema/Grammars/SQLiteGrammar.php - removed duplicate methods (now inherited via trait)

New Builder methods:

  • src/database/src/Query/Builder.php - new whereJsonContainsKey methods
  • src/database/src/Query/Grammars/MySqlGrammar.php - new compile method

Tests:

  • src/database/tests/QueryBuilderTest.php - MySQL tests + fixed array index expectation
  • src/database-pgsql/tests/Cases/DatabasePostgresQueryBuilderTest.php - new file with array index tests
  • src/database-sqlite/tests/DatabaseSQLiteQueryBuilderTest.php - renamed + array index tests

Add whereJsonContainsKey, orWhereJsonContainsKey, whereJsonDoesntContainKey,
and orWhereJsonDoesntContainKey methods to Query Builder.
Add compileJsonContainsKey method to base Grammar (throws by default) and
implement for MySQL, PostgreSQL, and SQLite drivers.
Add tests for MySQL, PostgreSQL, and SQLite grammars covering
whereJsonContainsKey, orWhereJsonContainsKey, whereJsonDoesntContainKey,
and orWhereJsonDoesntContainKey methods.
- Rename ContainerStub to SwooleExtContainerStub (tests deprecated extension)
- Rename DatabasePostgresBuilderTest to DatabasePostgresSwooleExtQueryBuilderTest
- Create new ContainerStub for PDO driver
- Create new DatabasePostgresQueryBuilderTest for PDO driver tests
- Rename DatabaseSQLiteQueryGrammarTest to DatabaseSQLiteQueryBuilderTest

The old names were inconsistent with the database package naming.
Schema Builder tests use "BuilderTest", Query Builder tests use "QueryBuilderTest".
Rename query builder test classes to match SchemaBuilderTest naming:
- DatabasePostgresQueryBuilderTest → QueryBuilderTest
- DatabasePostgresSwooleExtQueryBuilderTest → SwooleExtQueryBuilderTest
- DatabaseSQLiteQueryBuilderTest → QueryBuilderTest
…BuilderTest

Consistent with database and database-pgsql packages.
@binaryfire binaryfire changed the title feat: Port whereJsonContainsKey methods from Laravel + fix PostgreSQL / SQLite test structure feat: Port whereJsonContainsKey methods from Laravel + fix wrapJsonPath Jan 19, 2026
…dex support

- Update Grammar::wrapJsonPath() + add wrapJsonPathSegment() to properly parse
  array indices (e.g., `foo[0]` → `"foo"[0]` instead of `"foo[0]"`)
- Update PostgresGrammar::wrapJsonPathAttributes() + add parseJsonPathArrayKeys()
- Fix incorrect test expectation for array indices to match Laravel behavior
- Add array index tests for MySQL, PostgreSQL, and SQLite

Reference: laravel/framework DatabaseQueryBuilderTest.php:6682-6683
@binaryfire binaryfire changed the title feat: Port whereJsonContainsKey methods from Laravel + fix wrapJsonPath feat: Port whereJsonContainsKey methods + CompilesJsonPaths from Laravel Jan 19, 2026
…pilation

Port Laravel's CompilesJsonPaths trait pattern to eliminate code duplication
between Query Grammar and Schema Grammar classes.

- Create CompilesJsonPaths trait with wrapJsonFieldAndPath, wrapJsonPath,
  and wrapJsonPathSegment methods
- Use trait in Query Grammar (removes 51 lines of duplicated code)
- Use trait in Schema Grammar base class
- Remove duplicated methods from SQLite Schema Grammar (46 lines)
- Remove unused Str import from SQLite Schema Grammar
…ssing tests

- Fix compileJsonUpdateColumn to use wrapJsonPathAttributes for proper
  array index parsing in UPDATE queries
- Cast $i to int in compileJsonContainsKey for strict_types compatibility
- Add testMySqlUpdateWrappingJsonPathArrayIndex test
- Add testJsonPathEscaping test
- Add testPostgresUpdateWrappingJsonPathArrayIndex test
- Add testSQLiteUpdateWrappingJsonPathArrayIndex test
- Add PostgreSQL negative array index tests ([-1]) for whereJsonContainsKey
  and whereJsonDoesntContainKey
@limingxinleo
Copy link
Member

周末处理

@limingxinleo limingxinleo merged commit 2291270 into hyperf:master Jan 25, 2026
77 checks passed
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.

2 participants

Comments