Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.
Closed
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
29 changes: 27 additions & 2 deletions lib/MongoDB/BulkWriteView.pm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use MongoDB::_Types qw(
);
use Types::Standard qw(
Maybe
ArrayRef
InstanceOf
);
use boolean;
Expand All @@ -59,6 +60,11 @@ has _collation => (
isa => Maybe [Document],
);

has _array_filters => (
is => 'ro',
isa => Maybe [ArrayRef[Document]],
);

has _upsert => (
is => 'ro',
isa => Booleanpm,
Expand All @@ -74,6 +80,11 @@ sub collation {
return $self->new( %$self, _collation => $collation );
}

sub arrayFilters {
my ( $self, $array_filters ) = @_;
return $self->new( %$self, _array_filters => $array_filters );
}

sub upsert {
my ($self) = @_;
unless ( @_ == 1 ) {
Expand Down Expand Up @@ -122,6 +133,7 @@ sub _update {
upsert => boolean( $self->_upsert ),
is_replace => $method eq 'replace_one',
(defined $self->_collation ? (collation => $self->_collation) : ()),
(defined $self->_array_filters ? (arrayFilters => $self->_array_filters) : ()),
};

$self->_enqueue_write( [ update => $update ] );
Expand All @@ -137,6 +149,7 @@ sub delete_many {
q => $self->_query,
limit => 0,
( defined $self->_collation ? ( collation => $self->_collation ) : () ),
(defined $self->_array_filters ? (arrayFilters => $self->_array_filters) : ()),
}
]
);
Expand All @@ -151,6 +164,7 @@ sub delete_one {
q => $self->_query,
limit => 1,
( defined $self->_collation ? ( collation => $self->_collation ) : () ),
(defined $self->_array_filters ? (arrayFilters => $self->_array_filters) : ()),
}
]
);
Expand Down Expand Up @@ -224,6 +238,9 @@ __END__
# Remove all documents matching the selector
bulk->find( { a => 5 } )->delete_many();

# Update any arrays with the matching filter
bulk->find( {} )->arrayFilters([ { 'i.b' => 1 } ])->update_many( { '$set' => { 'y.$[i].b' => 2 } } );

# Remove all documents matching the selector, with respect to a collation
bulk->find( { a => { '$gte' => 'F' } )->collation($collation)->delete_many();

Expand All @@ -238,8 +255,16 @@ document.
To instantiate a C<MongoDB::BulkWriteView>, use the L<find|MongoDB::BulkWrite/find>
method from L<MongoDB::BulkWrite>.

Except for L</collation> and L</upsert>, all methods have an empty return on
success; an exception will be thrown on error.
Except for L</arrayFilters>, L</collation> and L</upsert>, all methods have an
empty return on success; an exception will be thrown on error.

=method arrayFilters

$bulk->arrayFilters( $array_filters )->update_many( $modification );

Returns a new C<MongoDB::BulkWriteView> object, where the specified arrayFilter
will be used to determine which array elements to modify for an update
operation on an array field.

=method collation

Expand Down
9 changes: 9 additions & 0 deletions lib/MongoDB/Collection.pm
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,9 @@ A hash reference of options may be provided.
Valid options include:

=for :list
* C<arrayFilters> - An array of filter documents that determines which array
elements to modify for an update operation on an array field. Only available
for MongoDB servers of version 3.6+.
* C<bypassDocumentValidation> - skips document validation, if enabled; this
is ignored for MongoDB servers older than version 3.2.
* C<collation> - a L<document|/Document> defining the collation for this operation.
Expand Down Expand Up @@ -615,6 +618,9 @@ A hash reference of options may be provided.
Valid options include:

=for :list
* C<arrayFilters> - An array of filter documents that determines which array
elements to modify for an update operation on an array field. Only available
for MongoDB servers of version 3.6+.
* C<bypassDocumentValidation> - skips document validation, if enabled; this
is ignored for MongoDB servers older than version 3.2.
* C<collation> - a L<document|/Document> defining the collation for this operation.
Expand Down Expand Up @@ -959,6 +965,9 @@ The update document must contain only field-update operators (e.g. C<$set>).
A hash reference of options may be provided. Valid keys include:

=for :list
* C<arrayFilters> - An array of filter documents that determines which array
elements to modify for an update operation on an array field. Only available
for MongoDB servers of version 3.6+.
* C<bypassDocumentValidation> - skips document validation, if enabled; this
is ignored for MongoDB servers older than version 3.2.
* C<collation> - a L<document|/Document> defining the collation for this operation.
Expand Down
16 changes: 16 additions & 0 deletions lib/MongoDB/Op/_Update.pm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use MongoDB::_Types qw(
use Types::Standard qw(
Bool
Maybe
ArrayRef
);
use Tie::IxHash;
use boolean;
Expand Down Expand Up @@ -71,6 +72,11 @@ has collation => (
isa => Maybe( [Document] ),
);

has arrayFilters => (
is => 'ro',
isa => Maybe( [ArrayRef[Document]] ),
);

with $_ for qw(
MongoDB::Role::_PrivateConstructor
MongoDB::Role::_CollectionOp
Expand All @@ -94,6 +100,15 @@ sub execute {
if !$self->write_concern->is_acknowledged;
}

if ( defined $self->arrayFilters ) {
MongoDB::UsageError->throw(
"MongoDB host '" . $link->address . "' doesn't support arrayFilters" )
if !$link->supports_arrayFilters;
MongoDB::UsageError->throw(
"Unacknowledged updates that specify arrayFilters are not allowed")
if !$self->write_concern->is_acknowledged;
}

my $orig_op = {
q => (
ref( $self->filter ) eq 'ARRAY'
Expand All @@ -104,6 +119,7 @@ sub execute {
multi => $self->multi ? $true : $false,
upsert => $self->upsert ? $true : $false,
( defined $self->collation ? ( collation => $self->collation ) : () ),
( defined $self->arrayFilters ? ( arrayFilters => $self->arrayFilters ) : () ),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec says there are various situations requiring errors: if the server is less than 3.6 or if it's an unacknowledged write with an opcode. You can mirror the "collation" logic in lines 94-101, but you'll need to define "supports_arrayFiltres" in MongoDB::Link as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error messages should be in now for that :)

};

return ! $self->write_concern->is_acknowledged
Expand Down
7 changes: 7 additions & 0 deletions lib/MongoDB/_Link.pm
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ has supports_collation => (
isa => Bool,
);

has supports_arrayFilters => (
is => 'rwp',
init_arg => undef,
isa => Bool,
);

my @connection_state_fields = qw(
fh connected rcvbuf last_used fdset is_ssl
);
Expand Down Expand Up @@ -235,6 +241,7 @@ sub set_metadata {

$self->_set_does_write_commands( $self->accepts_wire_version(2) );
$self->_set_supports_collation( $self->accepts_wire_version(5) );
$self->_set_supports_arrayFilters( $self->accepts_wire_version(6) );

return;
}
Expand Down
44 changes: 42 additions & 2 deletions t/crud_spec.t
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,25 @@ use version;
use MongoDB;

use lib "t/lib";
use MongoDBTest qw/skip_unless_mongod build_client get_test_db server_version server_type get_capped/;
use MongoDBTest qw/
skip_unless_mongod
build_client
get_test_db
server_version
server_type
get_feature_compat_version
/;

skip_unless_mongod();

my $conn = build_client();
my $testdb = get_test_db($conn);
my $server_version = server_version($conn);
my $server_type = server_type($conn);
my $feat_compat_ver = get_feature_compat_version($conn);
my $coll = $testdb->get_collection('test_collection');


for my $dir ( map { path("t/data/CRUD/$_") } qw/read write/ ) {
my $iterator = $dir->iterator( { recurse => 1 } );
while ( my $path = $iterator->() ) {
Expand All @@ -48,6 +57,9 @@ for my $dir ( map { path("t/data/CRUD/$_") } qw/read write/ ) {
my $name = $path->relative($dir)->basename(".json");

subtest $name => sub {
if ( $name =~ 'arrayFilter' && $feat_compat_ver < 3.6 ) {
plan skip_all => "arrayFilters requires featureCompatibilityVersion 3.6 - got $feat_compat_ver";
}
if ( exists $plan->{minServerVersion} ) {
my $min_version = $plan->{minServerVersion};
$min_version = "v$min_version" unless $min_version =~ /^v/;
Expand All @@ -69,7 +81,6 @@ for my $dir ( map { path("t/data/CRUD/$_") } qw/read write/ ) {
};
}
}

#--------------------------------------------------------------------------#
# generic tests
#--------------------------------------------------------------------------#
Expand Down Expand Up @@ -166,6 +177,35 @@ BEGIN {
# method-specific tests
#--------------------------------------------------------------------------#

sub test_bulk_write {
my ( $class, $label, $method, $args, $outcome ) = @_;

my $bulk;

if ( $args->{options}->{ordered} ) {
$bulk = $coll->initialize_ordered_bulk_op;
} else {
$bulk = $coll->initialize_unordered_bulk_op;
}

for my $request ( @{ $args->{requests} } ) {
my $req_method = $request->{name};
my $arg = $request->{arguments};
$req_method =~ s{([A-Z])}{_\L$1}g;
my $filter = delete $arg->{filter};
my $update = delete $arg->{update};
my $arr_filters = delete $arg->{arrayFilters};
my $bulk_view = $bulk->find( $filter );
if ( scalar( @$arr_filters ) ) {
$bulk_view = $bulk_view->arrayFilters( $arr_filters );
}
$bulk_view->$req_method( $update );
}
my $res = $bulk->execute;

check_write_outcome( $label, $res, $outcome );
}

sub test_aggregate {
my ( $class, $label, $method, $args, $outcome ) = @_;

Expand Down
23 changes: 7 additions & 16 deletions t/data/CRUD/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ define and setup. Therefore, these YAML tests are in no way a replacement for
more thorough testing. However, they can provide an initial verification of your
implementation.

Converting to JSON
==================

The tests are written in YAML because it is easier for humans to write and read,
and because YAML includes a standard comment format. A JSONified version of each
YAML file is included in this repository. Whenever a YAML file is modified, the
corresponding JSON file should be regenerated. One method to convert to JSON is
using `yamljs <https://www.npmjs.com/package/yamljs>`_::

npm install -g yamljs
yaml2json -s -p -r .

Version
=======

Expand Down Expand Up @@ -69,7 +57,11 @@ Each YAML file has the following keys:
the collection after the operation is executed. This will have some or all
of the following fields:

- ``result``: The return value from the operation.
- ``result``: The return value from the operation. Note that some tests
specify an ``upsertedCount`` field when the server does not provide
one in the result document. In these cases, an ``upsertedCount`` field
with a value of 0 should be manually added to the document received
from the server to facilitate comparison.

- ``collection``:

Expand All @@ -84,6 +76,5 @@ Use as integration tests

Running these as integration tests will require a running mongod server. Each of
these tests is valid against a standalone mongod, a replica set, and a sharded
system for server version 3.0.0. Many of them will run against 2.4 and 2.6, but
some will require conditional code. For instance, ``$out`` is not supported in
an aggregation pipeline in server 2.4, so that test must be skipped.
system for server version 3.0 and later. Many of them will run against 2.6, but
some will require conditional code.
2 changes: 1 addition & 1 deletion t/data/CRUD/read/aggregate-collation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
}
}
]
}
}
53 changes: 52 additions & 1 deletion t/data/CRUD/read/aggregate-out.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,57 @@
]
}
}
},
{
"description": "Aggregate with $out and batch size of 0",
"operation": {
"name": "aggregate",
"arguments": {
"pipeline": [
{
"$sort": {
"x": 1
}
},
{
"$match": {
"_id": {
"$gt": 1
}
}
},
{
"$out": "other_test_collection"
}
],
"batchSize": 0
}
},
"outcome": {
"result": [
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
}
],
"collection": {
"name": "other_test_collection",
"data": [
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
}
]
}
}
}
]
}
}
21 changes: 21 additions & 0 deletions t/data/CRUD/read/aggregate-out.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,24 @@ tests:
data:
- {_id: 2, x: 22}
- {_id: 3, x: 33}
-
description: "Aggregate with $out and batch size of 0"
operation:
name: aggregate
arguments:
pipeline:
- $sort: {x: 1}
- $match:
_id: {$gt: 1}
- $out: "other_test_collection"
batchSize: 0

outcome:
result:
- {_id: 2, x: 22}
- {_id: 3, x: 33}
collection:
name: "other_test_collection"
data:
- {_id: 2, x: 22}
- {_id: 3, x: 33}
Loading