Skip to content

Commit

Permalink
Support whereJsonOverlaps in query builders
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmitchell committed Nov 12, 2024
1 parent d25c9e3 commit ce0f13b
Show file tree
Hide file tree
Showing 8 changed files with 422 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,48 @@ public function orWhereJsonLength($column, $operator, $value = null)
return $this->whereJsonLength($column, $operator, $value, 'or');
}

public function whereJsonOverlaps($column, $values, $boolean = 'and')
{
if (! is_array($values)) {
$values = [$values];
}

$this->wheres[] = [
'type' => 'JsonOverlaps',
'column' => $column,
'values' => $values,
'boolean' => $boolean,
];

return $this;
}

public function orWhereJsonOverlaps($column, $values)
{
return $this->whereJsonOverlaps($column, $values, 'or');
}

public function whereJsonDoesntOverlap($column, $values, $boolean = 'and')
{
if (! is_array($values)) {
$values = [$values];
}

$this->wheres[] = [
'type' => 'JsonDoesntOverlap',
'column' => $column,
'values' => $values,
'boolean' => $boolean,
];

return $this;
}

public function orWhereJsonDoesntOverlap($column, $values)
{
return $this->whereJsonDoesntOverlap($column, $values, 'or');
}

public function whereNull($column, $boolean = 'and', $not = false)
{
$this->wheres[] = [
Expand Down
24 changes: 24 additions & 0 deletions src/Query/EloquentQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,30 @@ public function orWhereJsonLength($column, $operator, $value = null)
return $this->whereJsonLength($column, $operator, $value, 'or');
}

public function whereJsonOverlaps($column, $values, $boolean = 'and')
{
$this->builder->whereJsonOverlaps($this->column($column), $values, $boolean);

return $this;
}

public function orWhereJsonOverlaps($column, $values)
{
return $this->whereJsonOverlaps($column, $values, 'or');
}

public function whereJsonDoesntOverlap($column, $values, $boolean = 'and')
{
$this->builder->whereJsonDoesntOverlap($this->column($column), $values, $boolean);

return $this;
}

public function orWhereJsonDoesntOverlap($column, $values)
{
return $this->whereJsonDoesntOverlap($column, $values, 'or');
}

public function whereNull($column, $boolean = 'and', $not = false)
{
$this->builder->whereNull($this->column($column), $boolean, $not);
Expand Down
36 changes: 36 additions & 0 deletions src/Query/IteratorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Statamic\Query;

use Statamic\Support\Arr;

abstract class IteratorBuilder extends Builder
{
protected $randomize = false;
Expand Down Expand Up @@ -212,6 +214,40 @@ protected function filterWhereJsonLength($entries, $where)
});
}

protected function filterWhereJsonOverlaps($entries, $where)
{
return $entries->filter(function ($entry) use ($where) {
$value = $this->getFilterItemValue($entry, $where['column']);

if (is_null($value) || is_null($where['values'])) {
return false;
}

if (! is_array($value) && ! is_array($where['values'])) {
return $value === $where['values'];
}

return ! empty(array_intersect(Arr::wrap($value), $where['values']));
});
}

protected function filterWhereJsonDoesntOverlap($entries, $where)
{
return $entries->filter(function ($entry) use ($where) {
$value = $this->getFilterItemValue($entry, $where['column']);

if (is_null($value) || is_null($where['values'])) {
return true;
}

if (! is_array($value) && ! is_array($where['values'])) {
return $value !== $where['values'];
}

return empty(array_intersect(Arr::wrap($value), $where['values']));
});
}

protected function filterWhereDate($entries, $where)
{
$method = $this->operatorToCarbonMethod($where['operator']);
Expand Down
30 changes: 30 additions & 0 deletions src/Stache/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,36 @@ protected function filterWhereJsonLength($values, $where)
});
}

protected function filterWhereJsonOverlaps($values, $where)
{
return $values->filter(function ($value) use ($where) {
if (is_null($value) || is_null($where['values'])) {
return false;
}

if (! is_array($value) && ! is_array($where['values'])) {
return $value === $where['values'];
}

return ! empty(array_intersect(Arr::wrap($value), $where['values']));
});
}

protected function filterWhereJsonDoesntOverlap($values, $where)
{
return $values->filter(function ($value) use ($where) {
if (is_null($value) || is_null($where['values'])) {
return true;
}

if (! is_array($value) && ! is_array($where['values'])) {
return $value !== $where['values'];
}

return empty(array_intersect(Arr::wrap($value), $where['values']));
});
}

protected function filterWhereColumn($values, $where)
{
$whereColumnKeys = $this->getWhereColumnKeyValuesByIndex($where['value']);
Expand Down
72 changes: 72 additions & 0 deletions tests/Data/Assets/AssetQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,78 @@ public function assets_are_found_using_or_where_json_doesnt_contain()
$this->assertEquals(['a', 'c', 'b', 'd'], $assets->map->filename()->all());
}

#[Test]
public function assets_are_found_using_where_json_overlaps()
{
Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save();
Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save();
Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save();
Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save();
Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save();

$assets = $this->container->queryAssets()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1', 'taxonomy-5'])->get();

$this->assertCount(3, $assets);
$this->assertEquals(['a', 'c', 'e'], $assets->map->filename()->all());

$assets = $this->container->queryAssets()->whereJsonOverlaps('test_taxonomy', 'taxonomy-1')->get();

$this->assertCount(2, $assets);
$this->assertEquals(['a', 'c'], $assets->map->filename()->all());
}

#[Test]
public function assets_are_found_using_where_json_doesnt_overlap()
{
Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save();
Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save();
Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save();
Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save();
Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save();
Asset::find('test::f.jpg')->data(['test_taxonomy' => ['taxonomy-1']])->save();

$assets = $this->container->queryAssets()->whereJsonDoesntOverlap('test_taxonomy', ['taxonomy-1'])->get();

$this->assertCount(3, $assets);
$this->assertEquals(['b', 'd', 'e'], $assets->map->filename()->all());

$assets = $this->container->queryAssets()->whereJsonDoesntOverlap('test_taxonomy', 'taxonomy-1')->get();

$this->assertCount(3, $assets);
$this->assertEquals(['b', 'd', 'e'], $assets->map->filename()->all());
}

#[Test]
public function assets_are_found_using_or_where_json_overlaps()
{
Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save();
Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save();
Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save();
Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save();
Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save();

$assets = $this->container->queryAssets()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1'])->orWhereJsonOverlaps('test_taxonomy', ['taxonomy-5'])->get();

$this->assertCount(3, $assets);
$this->assertEquals(['a', 'c', 'e'], $assets->map->filename()->all());
}

#[Test]
public function assets_are_found_using_or_where_json_doesnt_overlap()
{
Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save();
Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save();
Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save();
Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save();
Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save();
Asset::find('test::f.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save();

$assets = $this->container->queryAssets()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1'])->orWhereJsonDoesntOverlap('test_taxonomy', ['taxonomy-5'])->get();

$this->assertCount(4, $assets);
$this->assertEquals(['a', 'c', 'b', 'd'], $assets->map->filename()->all());
}

#[Test]
public function assets_are_found_using_where_json_length()
{
Expand Down
70 changes: 70 additions & 0 deletions tests/Data/Entries/EntryQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,76 @@ public function entries_are_found_using_where_json_length()
$this->assertEquals(['Post 2', 'Post 5', 'Post 4'], $entries->map->title->all());
}

#[Test]
public function entries_are_found_using_where_json_overlaps()
{
EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->create();
EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'test_taxonomy' => ['taxonomy-3']])->create();
EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->create();
EntryFactory::id('4')->slug('post-4')->collection('posts')->data(['title' => 'Post 4', 'test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->create();
EntryFactory::id('5')->slug('post-5')->collection('posts')->data(['title' => 'Post 5', 'test_taxonomy' => ['taxonomy-5']])->create();

$entries = Entry::query()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1', 'taxonomy-5'])->get();

$this->assertCount(3, $entries);
$this->assertEquals(['Post 1', 'Post 3', 'Post 5'], $entries->map->title->all());

$entries = Entry::query()->whereJsonOverlaps('test_taxonomy', 'taxonomy-1')->get();

$this->assertCount(2, $entries);
$this->assertEquals(['Post 1', 'Post 3'], $entries->map->title->all());
}

#[Test]
public function entries_are_found_using_where_json_doesnt_overlap()
{
EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->create();
EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'test_taxonomy' => ['taxonomy-3']])->create();
EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->create();
EntryFactory::id('4')->slug('post-4')->collection('posts')->data(['title' => 'Post 4', 'test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->create();
EntryFactory::id('5')->slug('post-5')->collection('posts')->data(['title' => 'Post 5', 'test_taxonomy' => ['taxonomy-5']])->create();

$entries = Entry::query()->whereJsonDoesntOverlap('test_taxonomy', ['taxonomy-1'])->get();

$this->assertCount(3, $entries);
$this->assertEquals(['Post 2', 'Post 4', 'Post 5'], $entries->map->title->all());

$entries = Entry::query()->whereJsonDoesntOverlap('test_taxonomy', 'taxonomy-1')->get();

$this->assertCount(3, $entries);
$this->assertEquals(['Post 2', 'Post 4', 'Post 5'], $entries->map->title->all());
}

#[Test]
public function entries_are_found_using_or_where_json_overlaps()
{
EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->create();
EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'test_taxonomy' => ['taxonomy-3']])->create();
EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->create();
EntryFactory::id('4')->slug('post-4')->collection('posts')->data(['title' => 'Post 4', 'test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->create();
EntryFactory::id('5')->slug('post-5')->collection('posts')->data(['title' => 'Post 5', 'test_taxonomy' => ['taxonomy-5']])->create();

$entries = Entry::query()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1'])->orWhereJsonOverlaps('test_taxonomy', ['taxonomy-5'])->get();

$this->assertCount(3, $entries);
$this->assertEquals(['Post 1', 'Post 3', 'Post 5'], $entries->map->title->all());
}

#[Test]
public function entries_are_found_using_or_where_json_doesnt_overlap()
{
EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->create();
EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'test_taxonomy' => ['taxonomy-3']])->create();
EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->create();
EntryFactory::id('4')->slug('post-4')->collection('posts')->data(['title' => 'Post 4', 'test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->create();
EntryFactory::id('5')->slug('post-5')->collection('posts')->data(['title' => 'Post 5', 'test_taxonomy' => ['taxonomy-5']])->create();

$entries = Entry::query()->whereJsonOverlaps('test_taxonomy', ['taxonomy-1'])->orWhereJsonDoesntOverlap('test_taxonomy', ['taxonomy-5'])->get();

$this->assertCount(4, $entries);
$this->assertEquals(['Post 1', 'Post 3', 'Post 2', 'Post 4'], $entries->map->title->all());
}

#[Test]
public function entries_are_found_using_array_of_wheres()
{
Expand Down
Loading

0 comments on commit ce0f13b

Please sign in to comment.