From 815a7d7847cdaaf097e19f6227e9080817419380 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 1 Apr 2024 16:50:43 +0300 Subject: [PATCH 1/5] Add support json columns in orderBy --- CHANGELOG.md | 1 + src/Driver/Compiler.php | 18 ++++++++++++++++++ src/Driver/MySQL/MySQLCompiler.php | 7 +++++++ src/Driver/Postgres/PostgresCompiler.php | 6 ++++++ src/Driver/SQLServer/SQLServerCompiler.php | 6 ++++++ src/Driver/SQLite/SQLiteCompiler.php | 7 +++++++ .../Driver/MySQL/Query/SelectQueryTest.php | 13 +++++++++++++ .../Driver/Postgres/Query/SelectQueryTest.php | 10 ++++++++++ .../Driver/SQLServer/Query/SelectQueryTest.php | 13 +++++++++++++ .../Driver/SQLite/Query/SelectQueryTest.php | 13 +++++++++++++ 10 files changed, 94 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88711ab..c8c18fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ v2.10.0 (01.04.2024) -------------------- +- Add support **JSON** columns in **orderBy** statement by @msmakouz (#184) - Add `mediumText` column type by @msmakouz (#178) - Fix caching of SQL insert query with Fragment values by @msmakouz (#177) - Fix detection of enum values in PostgreSQL when a enum field has only one value by @msmakouz (#181) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index 3a68cdf1..94c068ba 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -22,6 +22,7 @@ abstract class Compiler implements CompilerInterface private Quoter $quoter; protected const ORDER_OPTIONS = ['ASC', 'DESC']; + private const JSON_SELECTOR = '->'; /** * @psalm-param non-empty-string $quotes @@ -246,6 +247,10 @@ protected function orderBy(QueryParameters $params, Quoter $q, array $orderBy): { $result = []; foreach ($orderBy as $order) { + if (\is_string($order[0]) && $this->isJsonSelector($order[0])) { + $order[0] = $this->compileJsonOrderBy($order[0]); + } + if ($order[1] === null) { $result[] = $this->name($params, $q, $order[0]); continue; @@ -531,6 +536,19 @@ protected function optional(string $prefix, string $expression, string $postfix return $prefix . $expression . $postfix; } + protected function isJsonSelector(string $selector): bool + { + return \str_contains($selector, self::JSON_SELECTOR); + } + + /** + * Each driver must override this method and implement sorting by JSON column. + */ + protected function compileJsonOrderBy(string $path): string|FragmentInterface + { + return $path; + } + private function arrayToInOperator(QueryParameters $params, Quoter $q, array $values, bool $in): string { $operator = $in ? 'IN' : 'NOT IN'; diff --git a/src/Driver/MySQL/MySQLCompiler.php b/src/Driver/MySQL/MySQLCompiler.php index 22587e7a..bf8e5d48 100644 --- a/src/Driver/MySQL/MySQLCompiler.php +++ b/src/Driver/MySQL/MySQLCompiler.php @@ -13,7 +13,9 @@ use Cycle\Database\Driver\CachingCompilerInterface; use Cycle\Database\Driver\Compiler; +use Cycle\Database\Driver\MySQL\Injection\CompileJson; use Cycle\Database\Driver\Quoter; +use Cycle\Database\Injection\FragmentInterface; use Cycle\Database\Injection\Parameter; use Cycle\Database\Query\QueryParameters; @@ -69,4 +71,9 @@ protected function limit(QueryParameters $params, Quoter $q, int $limit = null, return trim($statement); } + + protected function compileJsonOrderBy(string $path): FragmentInterface + { + return new CompileJson($path); + } } diff --git a/src/Driver/Postgres/PostgresCompiler.php b/src/Driver/Postgres/PostgresCompiler.php index 9df77d3e..133a1706 100644 --- a/src/Driver/Postgres/PostgresCompiler.php +++ b/src/Driver/Postgres/PostgresCompiler.php @@ -13,6 +13,7 @@ use Cycle\Database\Driver\CachingCompilerInterface; use Cycle\Database\Driver\Compiler; +use Cycle\Database\Driver\Postgres\Injection\CompileJson; use Cycle\Database\Driver\Quoter; use Cycle\Database\Injection\FragmentInterface; use Cycle\Database\Injection\Parameter; @@ -87,4 +88,9 @@ protected function limit(QueryParameters $params, Quoter $q, int $limit = null, return trim($statement); } + + protected function compileJsonOrderBy(string $path): FragmentInterface + { + return new CompileJson($path); + } } diff --git a/src/Driver/SQLServer/SQLServerCompiler.php b/src/Driver/SQLServer/SQLServerCompiler.php index 9b3816b4..1931c013 100644 --- a/src/Driver/SQLServer/SQLServerCompiler.php +++ b/src/Driver/SQLServer/SQLServerCompiler.php @@ -13,6 +13,7 @@ use Cycle\Database\Driver\Compiler; use Cycle\Database\Driver\Quoter; +use Cycle\Database\Driver\SQLServer\Injection\CompileJson; use Cycle\Database\Injection\Fragment; use Cycle\Database\Injection\FragmentInterface; use Cycle\Database\Injection\Parameter; @@ -158,6 +159,11 @@ protected function limit( return $statement; } + protected function compileJsonOrderBy(string $path): FragmentInterface + { + return new CompileJson($path); + } + /** * @inheritDoc */ diff --git a/src/Driver/SQLite/SQLiteCompiler.php b/src/Driver/SQLite/SQLiteCompiler.php index 882d3b80..7e1d5066 100644 --- a/src/Driver/SQLite/SQLiteCompiler.php +++ b/src/Driver/SQLite/SQLiteCompiler.php @@ -14,7 +14,9 @@ use Cycle\Database\Driver\CachingCompilerInterface; use Cycle\Database\Driver\Compiler; use Cycle\Database\Driver\Quoter; +use Cycle\Database\Driver\SQLite\Injection\CompileJson; use Cycle\Database\Exception\CompilerException; +use Cycle\Database\Injection\FragmentInterface; use Cycle\Database\Injection\Parameter; use Cycle\Database\Injection\ParameterInterface; use Cycle\Database\Query\QueryParameters; @@ -121,4 +123,9 @@ protected function insertQuery(QueryParameters $params, Quoter $q, array $tokens return implode("\n", $statement); } + + protected function compileJsonOrderBy(string $path): FragmentInterface + { + return new CompileJson($path); + } } diff --git a/tests/Database/Functional/Driver/MySQL/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/MySQL/Query/SelectQueryTest.php index db07e969..9bc25674 100644 --- a/tests/Database/Functional/Driver/MySQL/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/MySQL/Query/SelectQueryTest.php @@ -493,4 +493,17 @@ public function testOrderByCompileException(): void ->orderBy('name', 'FOO') ->sqlStatement(); } + + public function testOrderByJson(): void + { + $select = $this->database + ->select() + ->from('table') + ->orderBy('logs->created_at', 'DESC'); + + $this->assertSameQuery( + "SELECT * FROM {table} ORDER BY json_unquote(json_extract({logs}, '$.\"created_at\"')) DESC", + $select + ); + } } diff --git a/tests/Database/Functional/Driver/Postgres/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/Postgres/Query/SelectQueryTest.php index d9b83998..d7fa8fc5 100644 --- a/tests/Database/Functional/Driver/Postgres/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/Postgres/Query/SelectQueryTest.php @@ -503,4 +503,14 @@ public function testOrderByCompileException(): void ->orderBy('name', 'FOO') ->sqlStatement(); } + + public function testOrderByJson(): void + { + $select = $this->database + ->select() + ->from('table') + ->orderBy('logs->created_at', 'DESC'); + + $this->assertSameQuery("SELECT * FROM {table} ORDER BY {logs}->>'created_at' DESC", $select); + } } diff --git a/tests/Database/Functional/Driver/SQLServer/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/SQLServer/Query/SelectQueryTest.php index 33f9b959..541c641a 100644 --- a/tests/Database/Functional/Driver/SQLServer/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/SQLServer/Query/SelectQueryTest.php @@ -548,4 +548,17 @@ public function testSelectWithWhereJsonLengthNestedArray(): void ); $this->assertSameParameters([5], $select); } + + public function testOrderByJson(): void + { + $select = $this->database + ->select() + ->from('table') + ->orderBy('logs->created_at', 'DESC'); + + $this->assertSameQuery( + "SELECT * FROM {table} ORDER BY json_value({logs}, '$.\"created_at\"') DESC", + $select + ); + } } diff --git a/tests/Database/Functional/Driver/SQLite/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/SQLite/Query/SelectQueryTest.php index 085fae9b..4840a224 100644 --- a/tests/Database/Functional/Driver/SQLite/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/SQLite/Query/SelectQueryTest.php @@ -328,4 +328,17 @@ public function testSelectWithWhereJsonLengthNestedArray(): void ); $this->assertSameParameters([5], $select); } + + public function testOrderByJson(): void + { + $select = $this->database + ->select() + ->from('table') + ->orderBy('logs->created_at', 'DESC'); + + $this->assertSameQuery( + "SELECT * FROM {table} ORDER BY json_extract({logs}, '$.\"created_at\"') DESC", + $select + ); + } } From 6b6191cc3342f0bda40d6fb49e901257cf826c13 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 1 Apr 2024 17:08:55 +0300 Subject: [PATCH 2/5] Add unit test --- tests/Database/Unit/Driver/CompilerTest.php | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/Database/Unit/Driver/CompilerTest.php diff --git a/tests/Database/Unit/Driver/CompilerTest.php b/tests/Database/Unit/Driver/CompilerTest.php new file mode 100644 index 00000000..35d81302 --- /dev/null +++ b/tests/Database/Unit/Driver/CompilerTest.php @@ -0,0 +1,27 @@ +setAccessible(true); + + $this->assertSame('foo-bar', $ref->invoke($compiler, 'foo-bar')); + } +} From 37a3eec5a0601fc0e865545838d160c0f0e5463f Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 1 Apr 2024 17:13:07 +0300 Subject: [PATCH 3/5] Fix CS --- tests/Database/Unit/Driver/CompilerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Unit/Driver/CompilerTest.php b/tests/Database/Unit/Driver/CompilerTest.php index 35d81302..0b446850 100644 --- a/tests/Database/Unit/Driver/CompilerTest.php +++ b/tests/Database/Unit/Driver/CompilerTest.php @@ -13,7 +13,7 @@ final class CompilerTest extends TestCase { public function testCompileJsonOrderByShouldReturnOriginalStatement(): void { - $compiler = new class extends Compiler { + $compiler = new class () extends Compiler { protected function limit(QueryParameters $params, Quoter $q, int $limit = null, int $offset = null): string { } From 91748fe08dd34f92b06213f30e7040ff4cdb4889 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 2 Apr 2024 14:54:58 +0300 Subject: [PATCH 4/5] Rename method --- src/Driver/Compiler.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index 94c068ba..a6e0ac2e 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -22,7 +22,6 @@ abstract class Compiler implements CompilerInterface private Quoter $quoter; protected const ORDER_OPTIONS = ['ASC', 'DESC']; - private const JSON_SELECTOR = '->'; /** * @psalm-param non-empty-string $quotes @@ -247,7 +246,7 @@ protected function orderBy(QueryParameters $params, Quoter $q, array $orderBy): { $result = []; foreach ($orderBy as $order) { - if (\is_string($order[0]) && $this->isJsonSelector($order[0])) { + if (\is_string($order[0]) && $this->isJsonPath($order[0])) { $order[0] = $this->compileJsonOrderBy($order[0]); } @@ -536,9 +535,9 @@ protected function optional(string $prefix, string $expression, string $postfix return $prefix . $expression . $postfix; } - protected function isJsonSelector(string $selector): bool + protected function isJsonPath(string $selector): bool { - return \str_contains($selector, self::JSON_SELECTOR); + return \str_contains($selector, '->'); } /** From ec3d1ed38fa9295fc1ff0b2135c2300cb5831158 Mon Sep 17 00:00:00 2001 From: Aleksei Gagarin Date: Wed, 3 Apr 2024 10:45:43 +0400 Subject: [PATCH 5/5] Update src/Driver/Compiler.php --- src/Driver/Compiler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index a6e0ac2e..b3a8b269 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -535,9 +535,9 @@ protected function optional(string $prefix, string $expression, string $postfix return $prefix . $expression . $postfix; } - protected function isJsonPath(string $selector): bool + protected function isJsonPath(string $column): bool { - return \str_contains($selector, '->'); + return \str_contains($column, '->'); } /**