Skip to content

Commit 9d20d76

Browse files
committed
Merge branch 'master' of github.com:xp-forge/mongodb
2 parents 12ab317 + c00aff2 commit 9d20d76

File tree

3 files changed

+176
-6
lines changed

3 files changed

+176
-6
lines changed

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,28 @@ use com\mongodb\{MongoConnection, ObjectId};
2323
use util\cmd\Console;
2424

2525
$c= new MongoConnection('mongodb://localhost');
26-
$id= new ObjectId(...);
26+
$id= new ObjectId('...');
2727

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

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

3434
// Find all documents with a name of "Test"
35-
$cursor= $c->collection('test.products')->find(['name' => 'Test']);
35+
$cursor= $c->collection('test.products')->query(['name' => 'Test']);
36+
37+
// Use aggregation pipelines
38+
$cursor= $c->collection('test.products')->query([
39+
['$match' => ['color' => 'green', 'state' => 'ACTIVE']],
40+
['$lookup' => [
41+
'from' => 'users',
42+
'localField' => 'owner.id',
43+
'foreignField' => '_id',
44+
'as' => 'owner',
45+
]],
46+
['$addFields' => ['owner' => ['$first' => '$owner']]],
47+
]);
3648

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

6476
$c= new MongoConnection('mongodb://localhost');
65-
$id= new ObjectId(...);
77+
$id= new ObjectId('...');
6678

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

104116
$c= new MongoConnection('mongodb://localhost');
105-
$id= new ObjectId(...);
117+
$id= new ObjectId('...');
106118

107119
// Select a single document to be removed
108120
$result= $c->collection('test.products')->delete($id);

src/main/php/com/mongodb/Collection.class.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* A collection inside a database.
1010
*
1111
* @test com.mongodb.unittest.CollectionTest
12+
* @test com.mongodb.unittest.CollectionQueryTest
1213
*/
1314
class Collection implements Value {
1415
private $proto, $database, $name;
@@ -277,6 +278,55 @@ public function aggregate(array $pipeline= [], Options... $options): Cursor {
277278
return new Cursor($commands, $options, $result['body']['cursor']);
278279
}
279280

281+
/**
282+
* Runs a query and returns a cursor. The query may either be a string
283+
* or an object ID, in which case the `_id` member is matched, a map of
284+
* fields and match values which is passed to `find`, or an aggregation
285+
* pipeline.
286+
*
287+
* Note: The pipeline may not contain `$merge` or `$out` stages, this
288+
* method always uses read context for sending the query!
289+
*
290+
* @param string|com.mongodb.ObjectId|[:var]|[:var][] $query
291+
* @param com.mongodb.Options... $options
292+
* @return com.mongodb.result.Cursor
293+
* @throws com.mongodb.Error
294+
*/
295+
public function query($query= [], Options... $options) {
296+
$array= is_array($query);
297+
if ($array && 0 === key($query)) {
298+
$sections= [
299+
'aggregate' => $this->name,
300+
'pipeline' => $query,
301+
'cursor' => (object)[],
302+
'$db' => $this->database,
303+
];
304+
} else {
305+
$sections= [
306+
'find' => $this->name,
307+
'filter' => $array ? ($query ?: (object)[]) : ['_id' => $query],
308+
'$db' => $this->database,
309+
];
310+
}
311+
312+
$commands= Commands::reading($this->proto);
313+
$result= $commands->send($options, $sections);
314+
return new Cursor($commands, $options, $result['body']['cursor']);
315+
}
316+
317+
/**
318+
* Returns the first document for a given query, or NULL. Shorthand
319+
* for running `$collection->query($query)->first()`.
320+
*
321+
* @param ?string|com.mongodb.ObjectId|[:var]|[:var][] $query
322+
* @param com.mongodb.Options... $options
323+
* @return ?com.mongodb.Document
324+
* @throws com.mongodb.Error
325+
*/
326+
public function first($query= [], Options... $options) {
327+
return $this->query($query, ...$options)->first();
328+
}
329+
280330
/**
281331
* Watch for changes in this collection
282332
*
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php namespace com\mongodb\unittest;
2+
3+
use com\mongodb\{Collection, Document};
4+
use test\{Assert, Test};
5+
6+
class CollectionQueryTest {
7+
use WireTesting;
8+
9+
const FIRST= ['_id' => 'one', 'name' => 'A'];
10+
const SECOND= ['_id' => 'one', 'name' => 'A'];
11+
const DOCUMENTS= [self::FIRST, self::SECOND];
12+
const QUERY= ['$db' => 'testing', '$readPreference' => ['mode' => 'primary']];
13+
14+
/**
15+
* Returns a new fixture
16+
*
17+
* @param var... $messages
18+
* @return com.mongodb.io.Protocol
19+
*/
20+
private function newFixture(... $messages) {
21+
return $this->protocol([self::$PRIMARY => [$this->hello(self::$PRIMARY), ...$messages]], 'primary')->connect();
22+
}
23+
24+
#[Test]
25+
public function query() {
26+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
27+
Assert::equals(
28+
[new Document(self::FIRST), new Document(self::SECOND)],
29+
(new Collection($protocol, 'testing', 'tests'))->query()->all()
30+
);
31+
Assert::equals(
32+
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
33+
current($protocol->connections())->command(1)
34+
);
35+
}
36+
37+
#[Test]
38+
public function first() {
39+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
40+
Assert::equals(
41+
new Document(self::FIRST),
42+
(new Collection($protocol, 'testing', 'tests'))->first()
43+
);
44+
Assert::equals(
45+
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
46+
current($protocol->connections())->command(1)
47+
);
48+
}
49+
50+
#[Test]
51+
public function first_with_empty() {
52+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
53+
Assert::equals(
54+
new Document(self::FIRST),
55+
(new Collection($protocol, 'testing', 'tests'))->first([])
56+
);
57+
Assert::equals(
58+
['find' => 'tests', 'filter' => (object)[]] + self::QUERY,
59+
current($protocol->connections())->command(1)
60+
);
61+
}
62+
63+
#[Test]
64+
public function first_with_id() {
65+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
66+
Assert::equals(
67+
new Document(self::FIRST),
68+
(new Collection($protocol, 'testing', 'tests'))->first('one')
69+
);
70+
Assert::equals(
71+
['find' => 'tests', 'filter' => ['_id' => 'one']] + self::QUERY,
72+
current($protocol->connections())->command(1)
73+
);
74+
}
75+
76+
#[Test]
77+
public function first_with_query() {
78+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
79+
Assert::equals(
80+
new Document(self::FIRST),
81+
(new Collection($protocol, 'testing', 'tests'))->first(['_id' => 'one'])
82+
);
83+
Assert::equals(
84+
['find' => 'tests', 'filter' => ['_id' => 'one']] + self::QUERY,
85+
current($protocol->connections())->command(1)
86+
);
87+
}
88+
89+
#[Test]
90+
public function first_with_pipeline() {
91+
$pipeline= [['$match' => ['_id' => 'one']]];
92+
$protocol= $this->newFixture($this->cursor(self::DOCUMENTS));
93+
Assert::equals(
94+
new Document(self::FIRST),
95+
(new Collection($protocol, 'testing', 'tests'))->first($pipeline)
96+
);
97+
Assert::equals(
98+
['aggregate' => 'tests', 'pipeline' => $pipeline, 'cursor' => (object)[]] + self::QUERY,
99+
current($protocol->connections())->command(1)
100+
);
101+
}
102+
103+
#[Test]
104+
public function first_without_result() {
105+
$protocol= $this->newFixture($this->cursor([]));
106+
Assert::null((new Collection($protocol, 'testing', 'tests'))->first());
107+
}
108+
}

0 commit comments

Comments
 (0)