Skip to content

Commit 38755fa

Browse files
committed
BulkWriteCommandBuilder
1 parent ae8ba65 commit 38755fa

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

src/BulkWriteCommandBuilder.php

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
<?php
2+
/*
3+
* Copyright 2025-present MongoDB, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace MongoDB;
19+
20+
use MongoDB\Codec\DocumentCodec;
21+
use MongoDB\Codec\Encoder;
22+
use MongoDB\Driver\BulkWriteCommand;
23+
use MongoDB\Exception\InvalidArgumentException;
24+
25+
use function is_array;
26+
use function is_bool;
27+
use function is_string;
28+
29+
class BulkWriteCommandBuilder
30+
{
31+
private BulkWriteCommand $bulkWriteCommand;
32+
33+
private function __construct(
34+
private string $namespace,
35+
private Encoder $builderEncoder,
36+
private ?DocumentCodec $codec,
37+
array $options,
38+
) {
39+
$options += ['ordered' => true];
40+
41+
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
42+
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
43+
}
44+
45+
if (isset($options['let']) && ! is_document($options['let'])) {
46+
throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']);
47+
}
48+
49+
if (! is_bool($options['ordered'])) {
50+
throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
51+
}
52+
53+
if (isset($options['verboseResults']) && ! is_bool($options['verboseResults'])) {
54+
throw InvalidArgumentException::invalidType('"verboseResults" option', $options['verboseResults'], 'boolean');
55+
}
56+
57+
$this->bulkWriteCommand = new BulkWriteCommand($options);
58+
}
59+
60+
public static function createWithCollection(Collection $collection, array $options): self
61+
{
62+
return new self(
63+
$collection->getNamespace(),
64+
$collection->getBuilderEncoder(),
65+
$collection->getCodec(),
66+
$options,
67+
);
68+
}
69+
70+
public function withCollection(Collection $collection): self
71+
{
72+
$this->namespace = $collection->getNamespace();
73+
$this->builderEncoder = $collection->getBuilderEncoder();
74+
$this->codec = $collection->getCodec();
75+
76+
return $this;
77+
}
78+
79+
public function deleteOne(array|object $filter, ?array $options = null): self
80+
{
81+
$filter = $this->builderEncoder->encodeIfSupported($filter);
82+
83+
if (isset($options['collation']) && ! is_document($options['collation'])) {
84+
throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']);
85+
}
86+
87+
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) {
88+
throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']);
89+
}
90+
91+
$this->bulkWriteCommand->deleteOne($this->namespace, $filter, $options);
92+
93+
return $this;
94+
}
95+
96+
public function deleteMany(array|object $filter, ?array $options = null): self
97+
{
98+
$filter = $this->builderEncoder->encodeIfSupported($filter);
99+
100+
if (isset($options['collation']) && ! is_document($options['collation'])) {
101+
throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']);
102+
}
103+
104+
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) {
105+
throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']);
106+
}
107+
108+
$this->bulkWriteCommand->deleteMany($this->namespace, $filter, $options);
109+
110+
return $this;
111+
}
112+
113+
public function insertOne(array|object $document, mixed &$id = null): self
114+
{
115+
if ($this->codec) {
116+
$document = $this->codec->encode($document);
117+
}
118+
119+
// Capture the document's _id, which may have been generated, in an optional output variable
120+
$id = $this->bulkWriteCommand->insertOne($this->namespace, $document);
121+
122+
return $this;
123+
}
124+
125+
public function replaceOne(array|object $filter, array|object $replacement, ?array $options = null): self
126+
{
127+
$filter = $this->builderEncoder->encodeIfSupported($filter);
128+
129+
if ($this->codec) {
130+
$replacement = $this->codec->encode($replacement);
131+
}
132+
133+
// Treat empty arrays as replacement documents for BC
134+
if ($replacement === []) {
135+
$replacement = (object) $replacement;
136+
}
137+
138+
if (is_first_key_operator($replacement)) {
139+
throw new InvalidArgumentException('First key in $replacement is an update operator');
140+
}
141+
142+
if (is_pipeline($replacement, true)) {
143+
throw new InvalidArgumentException('$replacement is an update pipeline');
144+
}
145+
146+
if (isset($options['collation']) && ! is_document($options['collation'])) {
147+
throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']);
148+
}
149+
150+
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) {
151+
throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']);
152+
}
153+
154+
if (isset($options['sort']) && ! is_document($options['sort'])) {
155+
throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']);
156+
}
157+
158+
if (isset($options['upsert']) && ! is_bool($options['upsert'])) {
159+
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
160+
}
161+
162+
$this->bulkWriteCommand->replaceOne($this->namespace, $filter, $replacement, $options);
163+
164+
return $this;
165+
}
166+
167+
public function updateOne(array|object $filter, array|object $update, ?array $options = null): self
168+
{
169+
$filter = $this->builderEncoder->encodeIfSupported($filter);
170+
$update = $this->builderEncoder->encodeIfSupported($update);
171+
172+
if (! is_first_key_operator($update) && ! is_pipeline($update)) {
173+
throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update');
174+
}
175+
176+
if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) {
177+
throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array');
178+
}
179+
180+
if (isset($options['collation']) && ! is_document($options['collation'])) {
181+
throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']);
182+
}
183+
184+
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) {
185+
throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']);
186+
}
187+
188+
if (isset($options['sort']) && ! is_document($options['sort'])) {
189+
throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']);
190+
}
191+
192+
if (isset($options['upsert']) && ! is_bool($options['upsert'])) {
193+
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
194+
}
195+
196+
$this->bulkWriteCommand->updateOne($this->namespace, $filter, $update, $options);
197+
198+
return $this;
199+
}
200+
201+
public function updateMany(array|object $filter, array|object $update, ?array $options = null): self
202+
{
203+
$filter = $this->builderEncoder->encodeIfSupported($filter);
204+
$update = $this->builderEncoder->encodeIfSupported($update);
205+
206+
if (! is_first_key_operator($update) && ! is_pipeline($update)) {
207+
throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update');
208+
}
209+
210+
if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) {
211+
throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array');
212+
}
213+
214+
if (isset($options['collation']) && ! is_document($options['collation'])) {
215+
throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']);
216+
}
217+
218+
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) {
219+
throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']);
220+
}
221+
222+
if (isset($options['upsert']) && ! is_bool($options['upsert'])) {
223+
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
224+
}
225+
226+
$this->bulkWriteCommand->updateMany($this->namespace, $filter, $update, $options);
227+
228+
return $this;
229+
}
230+
}

src/Collection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,16 @@ public function findOneAndUpdate(array|object $filter, array|object $update, arr
753753
return $operation->execute(select_server_for_write($this->manager, $options));
754754
}
755755

756+
public function getBuilderEncoder(): BuilderEncoder
757+
{
758+
return $this->builderEncoder;
759+
}
760+
761+
public function getCodec(): ?DocumentCodec
762+
{
763+
return $this->codec;
764+
}
765+
756766
/**
757767
* Return the collection name.
758768
*/

0 commit comments

Comments
 (0)