Skip to content

Commit

Permalink
Merge pull request #2 from b2pweb/feature-collation-on-query
Browse files Browse the repository at this point in the history
Define options like collation on query
  • Loading branch information
Johnmeurt committed Jan 19, 2022
2 parents 38b6d2e + 37a7187 commit 66a5d9c
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 9 deletions.
9 changes: 5 additions & 4 deletions src/MongoDB/Query/Compiler/MongoCompiler.php
Expand Up @@ -114,7 +114,7 @@ protected function doCompileUpdate(CompilableClause $query)
'$set' => $data,
'$setOnInsert' => $filter
],
[
$query->statements['options'] + [
'upsert' => true,
'multi' => false
]
Expand All @@ -123,7 +123,7 @@ protected function doCompileUpdate(CompilableClause $query)
$bulk->update(
$this->grammar->filters($query, $query->statements['where']),
$this->compileUpdateOperators($query, $query->statements),
[
$query->statements['options'] + [
'multi' => true
]
);
Expand All @@ -142,7 +142,8 @@ protected function doCompileDelete(CompilableClause $query)
$bulk = new WriteQuery($query->statements['collection']);

$bulk->delete(
$this->grammar->filters($query, $query->statements['where'])
$this->grammar->filters($query, $query->statements['where']),
$query->statements['options']
);

return $bulk;
Expand All @@ -155,7 +156,7 @@ protected function doCompileDelete(CompilableClause $query)
*/
protected function doCompileSelect(CompilableClause $query)
{
$options = [];
$options = $query->statements['options'];

if ($query->statements['columns']) {
$options['projection'] = $this->grammar->projection($query, $query->statements['columns']);
Expand Down
9 changes: 6 additions & 3 deletions src/MongoDB/Query/Compiler/MongoKeyValueCompiler.php
Expand Up @@ -51,7 +51,7 @@ protected function doCompileUpdate(CompilableClause $query)
$bulk->update(
$this->compileFilters($query, $query->statements['where']),
$this->grammar->set($query, $query->statements['values']['data'], $query->statements['values']['types']),
[
$query->statements['options'] + [
'multi' => true
]
);
Expand All @@ -66,7 +66,10 @@ protected function doCompileDelete(CompilableClause $query)
{
$bulk = new WriteQuery($query->statements['collection']);

$bulk->delete($this->compileFilters($query, $query->statements['where']));
$bulk->delete(
$this->compileFilters($query, $query->statements['where']),
$query->statements['options']
);

return $bulk;
}
Expand All @@ -76,7 +79,7 @@ protected function doCompileDelete(CompilableClause $query)
*/
protected function doCompileSelect(CompilableClause $query)
{
$options = [];
$options = $query->statements['options'];

if ($query->statements['columns']) {
$options['projection'] = $this->grammar->projection($query, $query->statements['columns']);
Expand Down
4 changes: 3 additions & 1 deletion src/MongoDB/Query/MongoKeyValueQuery.php
Expand Up @@ -21,10 +21,11 @@
/**
* KeyValue query implementation for MongoDB
*/
final class MongoKeyValueQuery extends AbstractReadCommand implements KeyValueQueryInterface, Compilable, Paginable, Limitable
final class MongoKeyValueQuery extends AbstractReadCommand implements KeyValueQueryInterface, Compilable, Paginable, Limitable, OptionsConfigurable
{
use CompilableTrait;
use LimitableTrait;
use OptionsTrait;
use PaginableTrait;
use ProjectionableTrait;

Expand All @@ -51,6 +52,7 @@ public function __construct(ConnectionInterface $connection, PreprocessorInterfa
'data' => [],
'types' => [],
],
'options' => [],
];
}

Expand Down
4 changes: 3 additions & 1 deletion src/MongoDB/Query/MongoQuery.php
Expand Up @@ -24,10 +24,11 @@
* @property MongoConnection $connection
* @property MongoCompiler $compiler
*/
class MongoQuery extends AbstractQuery implements QueryInterface, Orderable, Paginable, Aggregatable, Limitable
class MongoQuery extends AbstractQuery implements QueryInterface, Orderable, Paginable, Aggregatable, Limitable, OptionsConfigurable
{
use PaginableTrait;
use LimitableTrait;
use OptionsTrait;
use OrderableTrait;


Expand All @@ -51,6 +52,7 @@ public function __construct(MongoConnection $connection, PreprocessorInterface $
'offset' => null,
'orders' => [],
'replace' => false,
'options' => [],
];
}

Expand Down
108 changes: 108 additions & 0 deletions src/MongoDB/Query/OptionsConfigurable.php
@@ -0,0 +1,108 @@
<?php

namespace Bdf\Prime\MongoDB\Query;

/**
* The query allow configuring options parameters
*
* Note: in mongo, options are not shared between write and querying, so be aware of the context when using it
*/
interface OptionsConfigurable
{
/**
* Define an option for the current query
*
* @param string $name The option name
* @param mixed $value The option value
*
* @return $this
*/
public function option(string $name, $value);

/**
* Define the collation to use on the current query
*
* Collation allows users to specify language-specific rules for string comparison,
* such as rules for lettercase and accent marks.
*
* To enable case-insensitive search or sort you can use `$query->collation(['locale' => 'en', 'strength' => 1])`
*
* This option is used by all query types
*
* @param array{
* locale: string,
* strength?: int,
* caseLevel?: bool,
* caseFirst?: "upper"|"lower"|"off",
* numericOrdering?: bool,
* alternate?: "non-ignorable"|"shifted",
* maxVariable?: "punct"|"space",
* backwards?: bool,
* normalization?: bool,
* } $collation
* @return mixed
*
* @see https://docs.mongodb.com/upcoming/reference/collation/#collation-document
*/
public function collation(array $collation);

/**
* Index specification.
* Specify either the index name as a string or the index key pattern.
* If specified, then the query system will only consider plans using the hinted index.
*
* This option is used by all query types
*
* @param string|array|object $hint
* @return $this
*/
public function hint($hint);

/**
* Update only the first matching document if false, or all matching documents true.
* This option cannot be true if newObj is a replacement document.
*
* This option is only supported by "update" query
*
* @param bool $flag
* @return $this
*/
public function multi(bool $flag = true);

/**
* If filter does not match an existing document, insert a single document.
* The document will be created from newObj if it is a replacement document (i.e. no update operators); otherwise,
* the operators in newObj will be applied to filter to create the new document.
*
* This option is only supported by "update" query
*
* @param bool $flag
* @return $this
*/
public function upsert(bool $flag = true);

/**
* An array of filter documents that determines which array elements to modify for an update operation on an array field.
*
* This option is only supported by "update" query
*
* @param array $filters
* @return $this
*
* @see https://docs.mongodb.com/manual/reference/command/update/#update-command-arrayfilters
*/
public function arrayFilters(array $filters);

/**
* Delete all matching documents (false), or only the first matching document (true)
*
* The configured option is "limit", which is in conflict with `Limitable::limit()` of the "select" query.
* So use this option only on "delete" query
*
* This option is only supported by "delete" query
*
* @param bool $flag
* @return $this
*/
public function onlyDeleteFirst(bool $flag = true);
}
69 changes: 69 additions & 0 deletions src/MongoDB/Query/OptionsTrait.php
@@ -0,0 +1,69 @@
<?php

namespace Bdf\Prime\MongoDB\Query;

/**
* Implements @see OptionsConfigurable
*
* @psalm-require-implements OptionsConfigurable
*/
trait OptionsTrait
{
/**
* {@inheritdoc}
*/
public function option(string $name, $value)
{
$this->statements['options'][$name] = $value;

return $this;
}

/**
* {@inheritdoc}
*/
public function collation(array $collation)
{
return $this->option('collation', $collation);
}

/**
* {@inheritdoc}
*/
public function hint($hint)
{
return $this->option('hint', $hint);
}

/**
* {@inheritdoc}
*/
public function multi(bool $flag = true)
{
return $this->option('multi', $flag);
}

/**
* {@inheritdoc}
*/
public function upsert(bool $flag = true)
{
return $this->option('upsert', $flag);
}

/**
* {@inheritdoc}
*/
public function arrayFilters(array $filters)
{
return $this->option('arrayFilters', $filters);
}

/**
* {@inheritdoc}
*/
public function onlyDeleteFirst(bool $flag = true)
{
return $this->option('limit', $flag);
}
}
15 changes: 15 additions & 0 deletions tests/MongoDB/Query/MongoKeyValueQueryTest.php
Expand Up @@ -349,6 +349,21 @@ public function test_aggregates()
$this->assertEquals([13, 42, 55, 22], $this->query()->from('aggregate')->aggregate('push', 'value'));
}

/**
*
*/
public function test_collation()
{
$result = $this->query()
->select(['name'])
->collation(['locale' => 'fr', 'strength' => 1])
->where('name.first', 'john')
->first()
;

$this->assertEquals(['name.first' => 'John', 'name.last' => 'Doe'], $result);
}

/**
* @return MongoKeyValueQuery
*/
Expand Down
46 changes: 46 additions & 0 deletions tests/MongoDB/Query/MongoQueryTest.php
Expand Up @@ -221,6 +221,19 @@ public function test_update()
$this->assertEquals(35, $this->query()->where('first_name', 'John')->inRow('age'));
}

/**
*
*/
public function test_update_multi_disabled()
{
$this->assertEquals(1, $this->query()
->multi(false)
->update(['age' => 35])
);

$this->assertEqualsCanonicalizing([35, false], $this->query()->inRows('age'));
}

/**
*
*/
Expand All @@ -234,6 +247,20 @@ public function test_delete()
$this->assertCount(1, $this->query()->all());
}


/**
*
*/
public function test_delete_with_onlyDeleteFirst()
{
$this->assertEquals(1, $this->query()
->onlyDeleteFirst()
->delete()
);

$this->assertCount(1, $this->query()->all());
}

/**
*
*/
Expand Down Expand Up @@ -299,6 +326,25 @@ public function test_order()
$this->assertEquals(['François', 'John'], $results);
}

/**
*
*/
public function test_order_with_collation()
{
$this->query()->insert(['first_name' => 'A']);
$this->query()->insert(['first_name' => 'b']);
$this->query()->insert(['first_name' => 'C']);
$this->query()->insert(['first_name' => 'd']);

$results = $this->query()
->collation(['locale' => 'fr', 'strength' => 1])
->order('first_name')
->inRows('first_name')
;

$this->assertEquals(['A', 'b', 'C', 'd', 'François', 'John'], $results);
}

/**
*
*/
Expand Down

0 comments on commit 66a5d9c

Please sign in to comment.