Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,28 @@ use com\mongodb\{MongoConnection, ObjectId};
use util\cmd\Console;

$c= new MongoConnection('mongodb://localhost');
$id= new ObjectId(...);
$id= new ObjectId('...');

// Find all documents
$cursor= $c->collection('test.products')->find();
$cursor= $c->collection('test.products')->query();

// Find document with the specified ID
$cursor= $c->collection('test.products')->find($id);
$cursor= $c->collection('test.products')->query($id);

// Find all documents with a name of "Test"
$cursor= $c->collection('test.products')->find(['name' => 'Test']);
$cursor= $c->collection('test.products')->query(['name' => 'Test']);

// Use aggregation pipelines
$cursor= $c->collection('test.products')->query([
['$match' => ['color' => 'green', 'state' => 'ACTIVE']],
['$lookup' => [
'from' => 'users',
'localField' => 'owner.id',
'foreignField' => '_id',
'as' => 'owner',
]],
['$addFields' => ['owner' => ['$first' => '$owner']]],
]);

foreach ($cursor as $document) {
Console::writeLine('>> ', $document);
Expand Down Expand Up @@ -62,7 +74,7 @@ use com\mongodb\{MongoConnection, ObjectId};
use util\cmd\Console;

$c= new MongoConnection('mongodb://localhost');
$id= new ObjectId(...);
$id= new ObjectId('...');

// Select a single document for updating by its ID
$result= $c->collection('test.products')->update($id, ['$inc' => ['qty' => 1]]);
Expand Down Expand Up @@ -102,7 +114,7 @@ use com\mongodb\{MongoConnection, ObjectId};
use util\cmd\Console;

$c= new MongoConnection('mongodb://localhost');
$id= new ObjectId(...);
$id= new ObjectId('...');

// Select a single document to be removed
$result= $c->collection('test.products')->delete($id);
Expand Down
50 changes: 50 additions & 0 deletions src/main/php/com/mongodb/Collection.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* A collection inside a database.
*
* @test com.mongodb.unittest.CollectionTest
* @test com.mongodb.unittest.CollectionQueryTest
*/
class Collection implements Value {
private $proto, $database, $name;
Expand Down Expand Up @@ -277,6 +278,55 @@ public function aggregate(array $pipeline= [], Options... $options): Cursor {
return new Cursor($commands, $options, $result['body']['cursor']);
}

/**
* Runs a query and returns a cursor. The query may either be a string
* or an object ID, in which case the `_id` member is matched, a map of
* fields and match values which is passed to `find`, or an aggregation
* pipeline.
*
* Note: The pipeline may not contain `$merge` or `$out` stages, this
* method always uses read context for sending the query!
*
* @param string|com.mongodb.ObjectId|[:var]|[:var][] $query
* @param com.mongodb.Options... $options
* @return com.mongodb.result.Cursor
* @throws com.mongodb.Error
*/
public function query($query= [], Options... $options) {
$array= is_array($query);
if ($array && 0 === key($query)) {
$sections= [
'aggregate' => $this->name,
'pipeline' => $query,
'cursor' => (object)[],
'$db' => $this->database,
];
} else {
$sections= [
'find' => $this->name,
'filter' => $array ? ($query ?: (object)[]) : ['_id' => $query],
'$db' => $this->database,
];
}

$commands= Commands::reading($this->proto);
$result= $commands->send($options, $sections);
return new Cursor($commands, $options, $result['body']['cursor']);
}

/**
* Returns the first document for a given query, or NULL. Shorthand
* for running `$collection->query($query)->first()`.
*
* @param ?string|com.mongodb.ObjectId|[:var]|[:var][] $query
* @param com.mongodb.Options... $options
* @return ?com.mongodb.Document
* @throws com.mongodb.Error
*/
public function first($query= [], Options... $options) {
return $this->query($query, ...$options)->first();
}

/**
* Watch for changes in this collection
*
Expand Down
108 changes: 108 additions & 0 deletions src/test/php/com/mongodb/unittest/CollectionQueryTest.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php namespace com\mongodb\unittest;

use com\mongodb\{Collection, Document};
use test\{Assert, Test};

class CollectionQueryTest {
use WireTesting;

const FIRST= ['_id' => 'one', 'name' => 'A'];
const SECOND= ['_id' => 'one', 'name' => 'A'];
const DOCUMENTS= [self::FIRST, self::SECOND];
const QUERY= ['$db' => 'testing', '$readPreference' => ['mode' => 'primary']];

/**
* Returns a new fixture
*
* @param var... $messages
* @return com.mongodb.io.Protocol
*/
private function newFixture(... $messages) {
return $this->protocol([self::$PRIMARY => [$this->hello(self::$PRIMARY), ...$messages]], 'primary')->connect();
}

#[Test]
public function query() {
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
[new Document(self::FIRST), new Document(self::SECOND)],
(new Collection($protocol, 'testing', 'tests'))->query()->all()
);
Assert::equals(
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first() {
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
new Document(self::FIRST),
(new Collection($protocol, 'testing', 'tests'))->first()
);
Assert::equals(
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first_with_empty() {
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
new Document(self::FIRST),
(new Collection($protocol, 'testing', 'tests'))->first([])
);
Assert::equals(
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first_with_id() {
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
new Document(self::FIRST),
(new Collection($protocol, 'testing', 'tests'))->first('one')
);
Assert::equals(
['find' => 'tests', 'filter' => ['_id' => 'one']] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first_with_query() {
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
new Document(self::FIRST),
(new Collection($protocol, 'testing', 'tests'))->first(['_id' => 'one'])
);
Assert::equals(
['find' => 'tests', 'filter' => ['_id' => 'one']] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first_with_pipeline() {
$pipeline= [['$match' => ['_id' => 'one']]];
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
Assert::equals(
new Document(self::FIRST),
(new Collection($protocol, 'testing', 'tests'))->first($pipeline)
);
Assert::equals(
['aggregate' => 'tests', 'pipeline' => $pipeline, 'cursor' => (object)[]] + self::QUERY,
current($protocol->connections())->command(1)
);
}

#[Test]
public function first_without_result() {
$protocol= $this->newFixture($this->cursor([]));
Assert::null((new Collection($protocol, 'testing', 'tests'))->first());
}
}
Loading