diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 8c8d48b..c4401ef 100755 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -1476,6 +1476,46 @@ public function orWhereJsonDoesntContain($column, $value) return $this->whereJsonDoesntContain($column, $value, 'or'); } + /** + * Add a "where JSON overlaps" clause to the query. + */ + public function whereJsonOverlaps(string $column, mixed $value, string $boolean = 'and', bool $not = false): static + { + $type = 'JsonOverlaps'; + + $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not'); + + if (! $value instanceof Expression) { + $this->addBinding($this->grammar->prepareBindingForJsonContains($value)); + } + + return $this; + } + + /** + * Add an "or where JSON overlaps" clause to the query. + */ + public function orWhereJsonOverlaps(string $column, mixed $value): static + { + return $this->whereJsonOverlaps($column, $value, 'or'); + } + + /** + * Add a "where JSON not overlap" clause to the query. + */ + public function whereJsonDoesntOverlap(string $column, mixed $value, string $boolean = 'and'): static + { + return $this->whereJsonOverlaps($column, $value, $boolean, true); + } + + /** + * Add an "or where JSON not overlap" clause to the query. + */ + public function orWhereJsonDoesntOverlap(string $column, mixed $value): static + { + return $this->whereJsonDoesntOverlap($column, $value, 'or'); + } + /** * Add a "where JSON length" clause to the query. * diff --git a/src/Query/Grammars/Grammar.php b/src/Query/Grammars/Grammar.php index c3f2a40..e4ba42a 100755 --- a/src/Query/Grammars/Grammar.php +++ b/src/Query/Grammars/Grammar.php @@ -371,6 +371,27 @@ public function compileJoinLateral(JoinLateralClause $join, string $expression): throw new RuntimeException('This database engine does not support lateral joins.'); } + /** + * Compile a "where JSON overlaps" clause. + */ + protected function whereJsonOverlaps(Builder $query, array $where): string + { + $not = $where['not'] ? 'not ' : ''; + + return $not . $this->compileJsonOverlaps( + $where['column'], + $this->parameter($where['value']) + ); + } + + /** + * Compile a "JSON overlaps" statement into SQL. + */ + protected function compileJsonOverlaps(string $column, string $value): string + { + throw new RuntimeException('This database engine does not support JSON overlaps operations.'); + } + /** * Compile the components necessary for a select clause. */ diff --git a/src/Query/Grammars/MySqlGrammar.php b/src/Query/Grammars/MySqlGrammar.php index 6506dcc..945d9e3 100755 --- a/src/Query/Grammars/MySqlGrammar.php +++ b/src/Query/Grammars/MySqlGrammar.php @@ -238,6 +238,16 @@ protected function compileJsonContains($column, $value): string return 'json_contains(' . $field . ', ' . $value . $path . ')'; } + /** + * Compile a "JSON overlaps" statement into SQL. + */ + protected function compileJsonOverlaps(string $column, string $value): string + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return 'json_overlaps(' . $field . ', ' . $value . $path . ')'; + } + /** * Compile a "JSON length" statement into SQL. * diff --git a/tests/DatabaseQueryBuilderTest.php b/tests/DatabaseQueryBuilderTest.php index d0debcf..1b51729 100644 --- a/tests/DatabaseQueryBuilderTest.php +++ b/tests/DatabaseQueryBuilderTest.php @@ -967,6 +967,37 @@ public function testOrWhereAny() $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); } + public function testWhereJsonOverlapsMySql(): void + { + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->whereJsonOverlaps('options', ['en', 'fr']); + $this->assertSame('select * from `users` where json_overlaps(`options`, ?)', $builder->toSql()); + $this->assertEquals(['["en","fr"]'], $builder->getBindings()); + + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->whereJsonOverlaps('users.options->languages', ['en', 'fr']); + $this->assertSame('select * from `users` where json_overlaps(`users`.`options`, ?, \'$."languages"\')', $builder->toSql()); + $this->assertEquals(['["en","fr"]'], $builder->getBindings()); + + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonOverlaps('options->languages', new Raw("'[\"en\", \"fr\"]'")); + $this->assertSame('select * from `users` where `id` = ? or json_overlaps(`options`, \'["en", "fr"]\', \'$."languages"\')', $builder->toSql()); + $this->assertEquals([1], $builder->getBindings()); + } + + public function testWhereJsonDoesntOverlapMySql(): void + { + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->whereJsonDoesntOverlap('options->languages', ['en', 'fr']); + $this->assertSame('select * from `users` where not json_overlaps(`options`, ?, \'$."languages"\')', $builder->toSql()); + $this->assertEquals(['["en","fr"]'], $builder->getBindings()); + + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonDoesntOverlap('options->languages', new Raw("'[\"en\", \"fr\"]'")); + $this->assertSame('select * from `users` where `id` = ? or not json_overlaps(`options`, \'["en", "fr"]\', \'$."languages"\')', $builder->toSql()); + $this->assertEquals([1], $builder->getBindings()); + } + public function testJoinLateral() { $builder = $this->getMySqlBuilder();