mongo 2.4.3 → 2.5.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -2
- data.tar.gz.sig +0 -0
- data/lib/mongo.rb +3 -2
- data/lib/mongo/auth/cr.rb +6 -4
- data/lib/mongo/auth/cr/conversation.rb +33 -17
- data/lib/mongo/auth/ldap.rb +4 -2
- data/lib/mongo/auth/ldap/conversation.rb +19 -9
- data/lib/mongo/auth/scram.rb +7 -4
- data/lib/mongo/auth/scram/conversation.rb +62 -24
- data/lib/mongo/auth/user.rb +10 -0
- data/lib/mongo/auth/user/view.rb +44 -22
- data/lib/mongo/auth/x509.rb +4 -2
- data/lib/mongo/auth/x509/conversation.rb +19 -9
- data/lib/mongo/bulk_write.rb +33 -27
- data/lib/mongo/bulk_write/combineable.rb +5 -0
- data/lib/mongo/bulk_write/transformable.rb +2 -0
- data/lib/mongo/bulk_write/validatable.rb +4 -0
- data/lib/mongo/client.rb +123 -12
- data/lib/mongo/cluster.rb +52 -11
- data/lib/mongo/cluster/app_metadata.rb +8 -2
- data/lib/mongo/cluster/cursor_reaper.rb +0 -1
- data/lib/mongo/cluster/topology.rb +1 -1
- data/lib/mongo/collection.rb +114 -27
- data/lib/mongo/collection/view.rb +8 -2
- data/lib/mongo/collection/view/aggregation.rb +11 -7
- data/lib/mongo/collection/view/builder/aggregation.rb +5 -1
- data/lib/mongo/collection/view/builder/find_command.rb +5 -3
- data/lib/mongo/collection/view/builder/map_reduce.rb +11 -3
- data/lib/mongo/collection/view/builder/op_query.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +160 -0
- data/lib/mongo/collection/view/change_stream/retryable.rb +57 -0
- data/lib/mongo/collection/view/iterable.rb +11 -10
- data/lib/mongo/collection/view/map_reduce.rb +22 -18
- data/lib/mongo/collection/view/readable.rb +51 -37
- data/lib/mongo/collection/view/writable.rb +72 -40
- data/lib/mongo/cursor.rb +25 -4
- data/lib/mongo/cursor/builder/get_more_command.rb +4 -2
- data/lib/mongo/database.rb +22 -11
- data/lib/mongo/database/view.rb +16 -12
- data/lib/mongo/error.rb +5 -0
- data/lib/mongo/error/invalid_session.rb +36 -0
- data/lib/mongo/error/missing_resume_token.rb +39 -0
- data/lib/mongo/error/operation_failure.rb +17 -0
- data/lib/mongo/error/parser.rb +3 -2
- data/lib/mongo/error/unknown_payload_type.rb +41 -0
- data/lib/mongo/error/unsupported_array_filters.rb +51 -0
- data/lib/mongo/error/unsupported_message_type.rb +23 -0
- data/lib/mongo/grid/fs_bucket.rb +5 -4
- data/lib/mongo/grid/stream/read.rb +3 -2
- data/lib/mongo/grid/stream/write.rb +2 -2
- data/lib/mongo/index/view.rb +35 -25
- data/lib/mongo/monitoring/event/secure.rb +14 -0
- data/lib/mongo/operation.rb +16 -0
- data/lib/mongo/operation/commands.rb +1 -0
- data/lib/mongo/operation/commands/aggregate.rb +9 -5
- data/lib/mongo/operation/commands/aggregate/result.rb +1 -1
- data/lib/mongo/operation/commands/collections_info.rb +6 -6
- data/lib/mongo/operation/commands/command.rb +2 -1
- data/lib/mongo/operation/commands/create.rb +6 -2
- data/lib/mongo/operation/commands/drop.rb +6 -2
- data/lib/mongo/operation/commands/drop_database.rb +6 -2
- data/lib/mongo/operation/commands/explain.rb +27 -0
- data/lib/mongo/operation/commands/explain/result.rb +52 -0
- data/lib/mongo/operation/commands/indexes.rb +1 -1
- data/lib/mongo/operation/commands/list_collections.rb +1 -1
- data/lib/mongo/operation/commands/list_collections/result.rb +1 -1
- data/lib/mongo/operation/commands/list_indexes.rb +1 -1
- data/lib/mongo/operation/commands/list_indexes/result.rb +1 -1
- data/lib/mongo/operation/commands/map_reduce.rb +8 -4
- data/lib/mongo/operation/commands/map_reduce/result.rb +13 -1
- data/lib/mongo/operation/commands/user_query.rb +1 -1
- data/lib/mongo/operation/commands/users_info.rb +6 -2
- data/lib/mongo/operation/executable.rb +4 -1
- data/lib/mongo/operation/read_preference.rb +10 -5
- data/lib/mongo/operation/result.rb +26 -2
- data/lib/mongo/operation/specifiable.rb +13 -1
- data/lib/mongo/operation/uses_command_op_msg.rb +47 -0
- data/lib/mongo/operation/write/bulk/bulkable.rb +4 -1
- data/lib/mongo/operation/write/bulk/insert/result.rb +4 -4
- data/lib/mongo/operation/write/command/create_index.rb +6 -1
- data/lib/mongo/operation/write/command/delete.rb +28 -4
- data/lib/mongo/operation/write/command/drop_index.rb +6 -1
- data/lib/mongo/operation/write/command/insert.rb +22 -18
- data/lib/mongo/operation/write/command/update.rb +24 -9
- data/lib/mongo/operation/write/command/writable.rb +14 -1
- data/lib/mongo/operation/write/insert.rb +4 -1
- data/lib/mongo/operation/write/insert/result.rb +2 -2
- data/lib/mongo/operation/write/update.rb +7 -1
- data/lib/mongo/operation/write/write_command_enabled.rb +20 -3
- data/lib/mongo/protocol.rb +3 -0
- data/lib/mongo/protocol/bit_vector.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +135 -0
- data/lib/mongo/protocol/delete.rb +8 -6
- data/lib/mongo/protocol/get_more.rb +8 -6
- data/lib/mongo/protocol/insert.rb +8 -6
- data/lib/mongo/protocol/kill_cursors.rb +8 -6
- data/lib/mongo/protocol/message.rb +31 -3
- data/lib/mongo/protocol/msg.rb +172 -0
- data/lib/mongo/protocol/query.rb +26 -6
- data/lib/mongo/protocol/registry.rb +76 -0
- data/lib/mongo/protocol/reply.rb +10 -5
- data/lib/mongo/protocol/serializers.rb +224 -0
- data/lib/mongo/protocol/update.rb +8 -6
- data/lib/mongo/retryable.rb +4 -2
- data/lib/mongo/server.rb +6 -3
- data/lib/mongo/server/connectable.rb +1 -1
- data/lib/mongo/server/connection.rb +30 -8
- data/lib/mongo/server/description.rb +25 -1
- data/lib/mongo/server/description/features.rb +4 -1
- data/lib/mongo/server/monitor.rb +5 -0
- data/lib/mongo/server/monitor/connection.rb +50 -2
- data/lib/mongo/server_selector/nearest.rb +10 -4
- data/lib/mongo/server_selector/primary.rb +20 -0
- data/lib/mongo/server_selector/primary_preferred.rb +10 -4
- data/lib/mongo/server_selector/secondary.rb +10 -4
- data/lib/mongo/server_selector/secondary_preferred.rb +24 -4
- data/lib/mongo/session.rb +180 -0
- data/lib/mongo/session/server_session.rb +73 -0
- data/lib/mongo/session/session_pool.rb +161 -0
- data/lib/mongo/uri.rb +11 -0
- data/lib/mongo/version.rb +1 -1
- data/mongo.gemspec +2 -1
- data/spec/mongo/auth/cr_spec.rb +12 -0
- data/spec/mongo/auth/ldap_spec.rb +2 -0
- data/spec/mongo/auth/scram/conversation_spec.rb +6 -6
- data/spec/mongo/auth/scram_spec.rb +25 -1
- data/spec/mongo/auth/user/view_spec.rb +268 -76
- data/spec/mongo/auth/x509_spec.rb +2 -0
- data/spec/mongo/bulk_write_spec.rb +435 -5
- data/spec/mongo/client_spec.rb +356 -39
- data/spec/mongo/cluster/app_metadata_spec.rb +2 -2
- data/spec/mongo/cluster_spec.rb +176 -0
- data/spec/mongo/collection/view/aggregation_spec.rb +33 -12
- data/spec/mongo/collection/view/builder/find_command_spec.rb +46 -6
- data/spec/mongo/collection/view/change_stream_spec.rb +814 -0
- data/spec/mongo/collection/view/map_reduce_spec.rb +94 -17
- data/spec/mongo/collection/view/readable_spec.rb +3 -12
- data/spec/mongo/collection_spec.rb +1048 -42
- data/spec/mongo/cursor/builder/get_more_command_spec.rb +19 -0
- data/spec/mongo/cursor_spec.rb +2 -2
- data/spec/mongo/database_spec.rb +50 -1
- data/spec/mongo/grid/fs_bucket_spec.rb +225 -137
- data/spec/mongo/grid/stream/read_spec.rb +2 -2
- data/spec/mongo/index/view_spec.rb +146 -8
- data/spec/mongo/monitoring/event/secure_spec.rb +42 -0
- data/spec/mongo/operation/read/query_spec.rb +2 -1
- data/spec/mongo/operation/specifiable_spec.rb +2 -2
- data/spec/mongo/operation/write/command/delete_spec.rb +96 -13
- data/spec/mongo/operation/write/command/insert_spec.rb +111 -12
- data/spec/mongo/operation/write/command/update_spec.rb +93 -10
- data/spec/mongo/operation/write/delete_spec.rb +1 -1
- data/spec/mongo/operation/write/insert_spec.rb +1 -1
- data/spec/mongo/operation/write/update_spec.rb +1 -1
- data/spec/mongo/protocol/compressed_spec.rb +66 -0
- data/spec/mongo/protocol/delete_spec.rb +14 -0
- data/spec/mongo/protocol/get_more_spec.rb +14 -0
- data/spec/mongo/protocol/insert_spec.rb +14 -0
- data/spec/mongo/protocol/kill_cursors_spec.rb +14 -0
- data/spec/mongo/protocol/msg_spec.rb +499 -0
- data/spec/mongo/protocol/query_spec.rb +45 -0
- data/spec/mongo/protocol/registry_spec.rb +31 -0
- data/spec/mongo/protocol/reply_spec.rb +14 -0
- data/spec/mongo/protocol/update_spec.rb +14 -0
- data/spec/mongo/retryable_spec.rb +6 -2
- data/spec/mongo/sdam_spec.rb +4 -0
- data/spec/mongo/server/connection_spec.rb +4 -2
- data/spec/mongo/server/description_spec.rb +28 -1
- data/spec/mongo/session/server_session_spec.rb +16 -0
- data/spec/mongo/session/session_pool_spec.rb +194 -0
- data/spec/mongo/uri_spec.rb +31 -2
- data/spec/spec_helper.rb +104 -0
- data/spec/support/authorization.rb +6 -1
- data/spec/support/crud.rb +3 -1
- data/spec/support/crud/write.rb +6 -1
- data/spec/support/crud_tests/write/findOneAndUpdate-arrayFilters.yml +69 -0
- data/spec/support/crud_tests/write/updateMany-arrayFilters.yml +63 -0
- data/spec/support/crud_tests/write/updateOne-arrayFilters.yml +109 -0
- data/spec/support/sdam/rs/discover_arbiters.yml +1 -1
- data/spec/support/sdam/rs/discover_passives.yml +2 -2
- data/spec/support/sdam/rs/discover_primary.yml +1 -1
- data/spec/support/sdam/rs/discover_secondary.yml +1 -1
- data/spec/support/sdam/rs/discovery.yml +4 -4
- data/spec/support/sdam/rs/equal_electionids.yml +1 -0
- data/spec/support/sdam/rs/ghost_discovered.yml +1 -1
- data/spec/support/sdam/rs/hosts_differ_from_seeds.yml +1 -1
- data/spec/support/sdam/rs/ls_timeout.yml +88 -0
- data/spec/support/sdam/rs/member_reconfig.yml +2 -2
- data/spec/support/sdam/rs/member_standalone.yml +2 -2
- data/spec/support/sdam/rs/new_primary.yml +2 -2
- data/spec/support/sdam/rs/new_primary_new_electionid.yml +3 -0
- data/spec/support/sdam/rs/new_primary_new_setversion.yml +3 -0
- data/spec/support/sdam/rs/new_primary_wrong_set_name.yml +2 -2
- data/spec/support/sdam/rs/non_rs_member.yml +1 -1
- data/spec/support/sdam/rs/normalize_case.yml +1 -1
- data/spec/support/sdam/rs/null_election_id.yml +4 -0
- data/spec/support/sdam/rs/primary_becomes_standalone.yml +2 -2
- data/spec/support/sdam/rs/primary_changes_set_name.yml +2 -2
- data/spec/support/sdam/rs/primary_disconnect.yml +2 -2
- data/spec/support/sdam/rs/primary_disconnect_electionid.yml +5 -0
- data/spec/support/sdam/rs/primary_disconnect_setversion.yml +5 -0
- data/spec/support/sdam/rs/primary_hint_from_secondary_with_mismatched_me.yml +58 -0
- data/spec/support/sdam/rs/primary_reports_new_member.yml +4 -4
- data/spec/support/sdam/rs/primary_to_no_primary_mismatched_me.yml +2 -2
- data/spec/support/sdam/rs/primary_wrong_set_name.yml +1 -1
- data/spec/support/sdam/rs/response_from_removed.yml +2 -2
- data/spec/support/sdam/rs/rsother_discovered.yml +1 -1
- data/spec/support/sdam/rs/sec_not_auth.yml +1 -1
- data/spec/support/sdam/rs/secondary_wrong_set_name.yml +1 -1
- data/spec/support/sdam/rs/secondary_wrong_set_name_with_primary.yml +2 -2
- data/spec/support/sdam/rs/setversion_without_electionid.yml +2 -0
- data/spec/support/sdam/rs/stepdown_change_set_name.yml +2 -2
- data/spec/support/sdam/rs/unexpected_mongos.yml +1 -1
- data/spec/support/sdam/rs/use_setversion_without_electionid.yml +3 -0
- data/spec/support/sdam/rs/wrong_set_name.yml +1 -1
- data/spec/support/sdam/sharded/ls_timeout_mongos.yml +97 -0
- data/spec/support/sdam/sharded/mongos_disconnect.yml +3 -3
- data/spec/support/sdam/sharded/multiple_mongoses.yml +1 -1
- data/spec/support/sdam/sharded/non_mongos_removed.yml +1 -1
- data/spec/support/sdam/sharded/normalize_uri_case.yml +1 -1
- data/spec/support/sdam/single/direct_connection_external_ip.yml +1 -1
- data/spec/support/sdam/single/direct_connection_mongos.yml +1 -1
- data/spec/support/sdam/single/direct_connection_rsarbiter.yml +1 -1
- data/spec/support/sdam/single/direct_connection_rsprimary.yml +1 -1
- data/spec/support/sdam/single/direct_connection_rssecondary.yml +1 -1
- data/spec/support/sdam/single/direct_connection_slave.yml +1 -1
- data/spec/support/sdam/single/direct_connection_standalone.yml +1 -1
- data/spec/support/sdam/single/ls_timeout_standalone.yml +35 -0
- data/spec/support/sdam/single/not_ok_response.yml +1 -1
- data/spec/support/sdam/single/standalone_removed.yml +1 -1
- data/spec/support/sdam/single/unavailable_seed.yml +1 -1
- data/spec/support/server_discovery_and_monitoring.rb +4 -0
- data/spec/support/shared/session.rb +236 -0
- metadata +53 -15
- metadata.gz.sig +0 -0
@@ -95,8 +95,8 @@ describe Mongo::Cluster::AppMetadata do
|
|
95
95
|
allow(app_metadata).to receive(:driver_doc).and_return('x'*500)
|
96
96
|
end
|
97
97
|
|
98
|
-
it 'truncates the document to be just an ismaster command' do
|
99
|
-
expect(app_metadata.ismaster_bytes.length).to eq(Mongo::Server::Monitor::Connection::ISMASTER_BYTES.length)
|
98
|
+
it 'truncates the document to be just an ismaster command and the compressors', unless: compression_enabled? do
|
99
|
+
expect(app_metadata.ismaster_bytes.length).to eq(Mongo::Server::Monitor::Connection::ISMASTER_BYTES.length + 26)
|
100
100
|
end
|
101
101
|
end
|
102
102
|
end
|
data/spec/mongo/cluster_spec.rb
CHANGED
@@ -510,4 +510,180 @@ describe Mongo::Cluster do
|
|
510
510
|
end
|
511
511
|
end
|
512
512
|
end
|
513
|
+
|
514
|
+
describe '#logical_session_timeout' do
|
515
|
+
|
516
|
+
let(:listeners) do
|
517
|
+
Mongo::Event::Listeners.new
|
518
|
+
end
|
519
|
+
|
520
|
+
let(:monitoring) do
|
521
|
+
Mongo::Monitoring.new(monitoring: false)
|
522
|
+
end
|
523
|
+
|
524
|
+
let(:server_one) do
|
525
|
+
Mongo::Server.new(default_address, cluster, monitoring, listeners)
|
526
|
+
end
|
527
|
+
|
528
|
+
let(:server_two) do
|
529
|
+
Mongo::Server.new(default_address, cluster, monitoring, listeners)
|
530
|
+
end
|
531
|
+
|
532
|
+
let(:servers) do
|
533
|
+
[ server_one, server_two ]
|
534
|
+
end
|
535
|
+
|
536
|
+
before do
|
537
|
+
allow(cluster).to receive(:servers).and_return(servers)
|
538
|
+
end
|
539
|
+
|
540
|
+
context 'when one server has a nil logical session timeout value' do
|
541
|
+
|
542
|
+
before do
|
543
|
+
allow(server_one).to receive(:logical_session_timeout).and_return(7)
|
544
|
+
allow(server_two).to receive(:logical_session_timeout).and_return(nil)
|
545
|
+
end
|
546
|
+
|
547
|
+
it 'returns nil' do
|
548
|
+
expect(cluster.logical_session_timeout).to be(nil)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
context 'when all servers have a logical session timeout value' do
|
553
|
+
|
554
|
+
before do
|
555
|
+
allow(server_one).to receive(:logical_session_timeout).and_return(7)
|
556
|
+
allow(server_two).to receive(:logical_session_timeout).and_return(3)
|
557
|
+
end
|
558
|
+
|
559
|
+
it 'returns the minimum' do
|
560
|
+
expect(cluster.logical_session_timeout).to be(3)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context 'when no servers have a logical session timeout value' do
|
565
|
+
|
566
|
+
before do
|
567
|
+
allow(server_one).to receive(:logical_session_timeout).and_return(nil)
|
568
|
+
allow(server_two).to receive(:logical_session_timeout).and_return(nil)
|
569
|
+
end
|
570
|
+
|
571
|
+
it 'returns nil' do
|
572
|
+
expect(cluster.logical_session_timeout).to be(nil)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
describe '#cluster_time' do
|
578
|
+
|
579
|
+
let(:operation) do
|
580
|
+
client.command(ping: 1)
|
581
|
+
end
|
582
|
+
|
583
|
+
let(:second_operation) do
|
584
|
+
client.command(ping: 1)
|
585
|
+
end
|
586
|
+
|
587
|
+
it_behaves_like 'an operation updating cluster time'
|
588
|
+
end
|
589
|
+
|
590
|
+
describe '#update_cluster_time' do
|
591
|
+
|
592
|
+
let(:cluster) do
|
593
|
+
described_class.new(ADDRESSES, monitoring, TEST_OPTIONS.merge(heartbeat_frequency: 1000))
|
594
|
+
end
|
595
|
+
|
596
|
+
let(:result) do
|
597
|
+
double('result', cluster_time: cluster_time_doc)
|
598
|
+
end
|
599
|
+
|
600
|
+
context 'when the cluster_time variable is nil' do
|
601
|
+
|
602
|
+
before do
|
603
|
+
cluster.instance_variable_set(:@cluster_time, nil)
|
604
|
+
cluster.update_cluster_time(result)
|
605
|
+
end
|
606
|
+
|
607
|
+
context 'when the cluster time received is nil' do
|
608
|
+
|
609
|
+
let(:cluster_time_doc) do
|
610
|
+
nil
|
611
|
+
end
|
612
|
+
|
613
|
+
it 'does not set the cluster_time variable' do
|
614
|
+
expect(cluster.cluster_time).to be_nil
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
context 'when the cluster time received is not nil' do
|
619
|
+
|
620
|
+
let(:cluster_time_doc) do
|
621
|
+
BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'sets the cluster_time variable to the cluster time doc' do
|
625
|
+
expect(cluster.cluster_time).to eq(cluster_time_doc)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
context 'when the cluster_time variable has a value' do
|
631
|
+
|
632
|
+
before do
|
633
|
+
cluster.instance_variable_set(:@cluster_time, BSON::Document.new(
|
634
|
+
Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
|
635
|
+
cluster.update_cluster_time(result)
|
636
|
+
end
|
637
|
+
|
638
|
+
context 'when the cluster time received is nil' do
|
639
|
+
|
640
|
+
let(:cluster_time_doc) do
|
641
|
+
nil
|
642
|
+
end
|
643
|
+
|
644
|
+
it 'does not update the cluster_time variable' do
|
645
|
+
expect(cluster.cluster_time).to eq(BSON::Document.new(
|
646
|
+
Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
context 'when the cluster time received is not nil' do
|
651
|
+
|
652
|
+
context 'when the cluster time received is greater than the cluster_time variable' do
|
653
|
+
|
654
|
+
let(:cluster_time_doc) do
|
655
|
+
BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 2))
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'sets the cluster_time variable to the cluster time' do
|
659
|
+
expect(cluster.cluster_time).to eq(cluster_time_doc)
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
context 'when the cluster time received is less than the cluster_time variable' do
|
664
|
+
|
665
|
+
let(:cluster_time_doc) do
|
666
|
+
BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(0, 1))
|
667
|
+
end
|
668
|
+
|
669
|
+
it 'does not set the cluster_time variable to the cluster time' do
|
670
|
+
expect(cluster.cluster_time).to eq(BSON::Document.new(
|
671
|
+
Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
context 'when the cluster time received is equal to the cluster_time variable' do
|
676
|
+
|
677
|
+
let(:cluster_time_doc) do
|
678
|
+
BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))
|
679
|
+
end
|
680
|
+
|
681
|
+
it 'does not change the cluster_time variable' do
|
682
|
+
expect(cluster.cluster_time).to eq(BSON::Document.new(
|
683
|
+
Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
|
684
|
+
end
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
513
689
|
end
|
@@ -26,6 +26,10 @@ describe Mongo::Collection::View::Aggregation do
|
|
26
26
|
described_class.new(view, pipeline, options)
|
27
27
|
end
|
28
28
|
|
29
|
+
let(:aggregation_spec) do
|
30
|
+
aggregation.send(:aggregate_spec, double('session'))
|
31
|
+
end
|
32
|
+
|
29
33
|
after do
|
30
34
|
authorized_collection.delete_many
|
31
35
|
end
|
@@ -68,6 +72,23 @@ describe Mongo::Collection::View::Aggregation do
|
|
68
72
|
authorized_collection.delete_many
|
69
73
|
end
|
70
74
|
|
75
|
+
context 'when provided a session' do
|
76
|
+
|
77
|
+
let(:options) do
|
78
|
+
{ session: session }
|
79
|
+
end
|
80
|
+
|
81
|
+
let(:operation) do
|
82
|
+
aggregation.to_a
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:client) do
|
86
|
+
authorized_client
|
87
|
+
end
|
88
|
+
|
89
|
+
it_behaves_like 'an operation using a session'
|
90
|
+
end
|
91
|
+
|
71
92
|
context 'when a block is provided' do
|
72
93
|
|
73
94
|
context 'when no batch size is provided' do
|
@@ -280,7 +301,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
280
301
|
|
281
302
|
it 'includes the read preference in the spec' do
|
282
303
|
allow(authorized_collection).to receive(:read_preference).and_return(read_preference)
|
283
|
-
expect(
|
304
|
+
expect(aggregation_spec[:read]).to eq(read_preference)
|
284
305
|
end
|
285
306
|
end
|
286
307
|
|
@@ -291,7 +312,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
291
312
|
end
|
292
313
|
|
293
314
|
it 'includes the option in the spec' do
|
294
|
-
expect(
|
315
|
+
expect(aggregation_spec[:selector][:allowDiskUse]).to eq(true)
|
295
316
|
end
|
296
317
|
|
297
318
|
context 'when allow_disk_use is specified as an option' do
|
@@ -305,7 +326,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
305
326
|
end
|
306
327
|
|
307
328
|
it 'includes the option in the spec' do
|
308
|
-
expect(
|
329
|
+
expect(aggregation_spec[:selector][:allowDiskUse]).to eq(true)
|
309
330
|
end
|
310
331
|
|
311
332
|
context 'when #allow_disk_use is also called' do
|
@@ -319,7 +340,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
319
340
|
end
|
320
341
|
|
321
342
|
it 'overrides the first option with the second' do
|
322
|
-
expect(
|
343
|
+
expect(aggregation_spec[:selector][:allowDiskUse]).to eq(false)
|
323
344
|
end
|
324
345
|
end
|
325
346
|
end
|
@@ -332,7 +353,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
332
353
|
end
|
333
354
|
|
334
355
|
it 'includes the option in the spec' do
|
335
|
-
expect(
|
356
|
+
expect(aggregation_spec[:selector][:maxTimeMS]).to eq(options[:max_time_ms])
|
336
357
|
end
|
337
358
|
end
|
338
359
|
|
@@ -345,7 +366,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
345
366
|
end
|
346
367
|
|
347
368
|
it 'uses the batch_size on the view' do
|
348
|
-
expect(
|
369
|
+
expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(view_options[:batch_size])
|
349
370
|
end
|
350
371
|
end
|
351
372
|
|
@@ -356,7 +377,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
356
377
|
end
|
357
378
|
|
358
379
|
it 'includes the option in the spec' do
|
359
|
-
expect(
|
380
|
+
expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
|
360
381
|
end
|
361
382
|
|
362
383
|
context 'when batch_size is also set on the view' do
|
@@ -366,7 +387,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
366
387
|
end
|
367
388
|
|
368
389
|
it 'overrides the view batch_size with the option batch_size' do
|
369
|
-
expect(
|
390
|
+
expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
|
370
391
|
end
|
371
392
|
end
|
372
393
|
end
|
@@ -379,7 +400,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
379
400
|
end
|
380
401
|
|
381
402
|
it 'includes the option in the spec' do
|
382
|
-
expect(
|
403
|
+
expect(aggregation_spec[:selector][:hint]).to eq(options['hint'])
|
383
404
|
end
|
384
405
|
end
|
385
406
|
|
@@ -396,7 +417,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
396
417
|
end
|
397
418
|
|
398
419
|
it 'sets a batch size document in the spec' do
|
399
|
-
expect(
|
420
|
+
expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
|
400
421
|
end
|
401
422
|
end
|
402
423
|
|
@@ -407,7 +428,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
407
428
|
end
|
408
429
|
|
409
430
|
it 'sets an empty document in the spec' do
|
410
|
-
expect(
|
431
|
+
expect(aggregation_spec[:selector][:cursor]).to eq({})
|
411
432
|
end
|
412
433
|
end
|
413
434
|
|
@@ -422,7 +443,7 @@ describe Mongo::Collection::View::Aggregation do
|
|
422
443
|
context 'when batch_size is set' do
|
423
444
|
|
424
445
|
it 'does not set the cursor option in the spec' do
|
425
|
-
expect(
|
446
|
+
expect(aggregation_spec[:selector][:cursor]).to be_nil
|
426
447
|
end
|
427
448
|
end
|
428
449
|
end
|
@@ -9,7 +9,7 @@ describe Mongo::Collection::View::Builder::FindCommand do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
let(:builder) do
|
12
|
-
described_class.new(view)
|
12
|
+
described_class.new(view, nil)
|
13
13
|
end
|
14
14
|
|
15
15
|
let(:specification) do
|
@@ -48,11 +48,25 @@ describe Mongo::Collection::View::Builder::FindCommand do
|
|
48
48
|
no_cursor_timeout: true,
|
49
49
|
await_data: true,
|
50
50
|
allow_partial_results: true,
|
51
|
-
read_concern: { level: 'local' },
|
52
51
|
collation: { locale: 'en_US' }
|
53
52
|
}
|
54
53
|
end
|
55
54
|
|
55
|
+
context 'when the operation has a session' do
|
56
|
+
|
57
|
+
let(:session) do
|
58
|
+
double('session')
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:builder) do
|
62
|
+
described_class.new(view, session)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'adds the session to the specification' do
|
66
|
+
expect(builder.specification[:session]).to be(session)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
56
70
|
it 'maps the collection name' do
|
57
71
|
expect(selector['find']).to eq(authorized_collection.name)
|
58
72
|
end
|
@@ -109,10 +123,6 @@ describe Mongo::Collection::View::Builder::FindCommand do
|
|
109
123
|
expect(selector['min']).to eq('name' => 'albert')
|
110
124
|
end
|
111
125
|
|
112
|
-
it 'maps read concern' do
|
113
|
-
expect(selector['readConcern']).to eq('level' => 'local')
|
114
|
-
end
|
115
|
-
|
116
126
|
it 'maps return key' do
|
117
127
|
expect(selector['returnKey']).to be true
|
118
128
|
end
|
@@ -451,5 +461,35 @@ describe Mongo::Collection::View::Builder::FindCommand do
|
|
451
461
|
end
|
452
462
|
end
|
453
463
|
end
|
464
|
+
|
465
|
+
context 'when the collection has a read concern defined' do
|
466
|
+
|
467
|
+
let(:collection) do
|
468
|
+
authorized_collection.with(read_concern: { level: 'invalid' })
|
469
|
+
end
|
470
|
+
|
471
|
+
let(:view) do
|
472
|
+
Mongo::Collection::View.new(collection, {})
|
473
|
+
end
|
474
|
+
|
475
|
+
it 'applies the read concern of the collection' do
|
476
|
+
expect(selector['readConcern']).to eq(BSON::Document.new(level: 'invalid'))
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
context 'when the collection does not have a read concern defined' do
|
481
|
+
|
482
|
+
let(:filter) do
|
483
|
+
{}
|
484
|
+
end
|
485
|
+
|
486
|
+
let(:options) do
|
487
|
+
{}
|
488
|
+
end
|
489
|
+
|
490
|
+
it 'does not apply a read concern' do
|
491
|
+
expect(selector['readConcern']).to be_nil
|
492
|
+
end
|
493
|
+
end
|
454
494
|
end
|
455
495
|
end
|
@@ -0,0 +1,814 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongo::Collection::View::ChangeStream, if: test_change_streams? do
|
4
|
+
|
5
|
+
let(:pipeline) do
|
6
|
+
[]
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:options) do
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:view_options) do
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:view) do
|
18
|
+
Mongo::Collection::View.new(authorized_collection, {}, view_options)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:change_stream) do
|
22
|
+
described_class.new(view, pipeline, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:change_stream_document) do
|
26
|
+
change_stream.send(:pipeline)[0]['$changeStream']
|
27
|
+
end
|
28
|
+
|
29
|
+
let!(:sample_resume_token) do
|
30
|
+
stream = authorized_collection.watch
|
31
|
+
authorized_collection.insert_one(a: 1)
|
32
|
+
doc = stream.to_enum.next
|
33
|
+
stream.close
|
34
|
+
doc[:_id]
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:command_selector) do
|
38
|
+
change_stream.send(:aggregate_spec, double('session'))[:selector]
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:cursor) do
|
42
|
+
change_stream.instance_variable_get(:@cursor)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:error) do
|
46
|
+
begin
|
47
|
+
change_stream
|
48
|
+
rescue => e
|
49
|
+
e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
after do
|
54
|
+
authorized_collection.delete_many
|
55
|
+
begin; change_stream.close; rescue; end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#initialize' do
|
59
|
+
|
60
|
+
it 'sets the view' do
|
61
|
+
expect(change_stream.view).to be(view)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'sets the options' do
|
65
|
+
expect(change_stream.options).to eq(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when full_document is provided' do
|
69
|
+
|
70
|
+
context "when the value is 'default'" do
|
71
|
+
|
72
|
+
let(:options) do
|
73
|
+
{ full_document: 'default' }
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'sets the fullDocument value to default' do
|
77
|
+
expect(change_stream_document[:fullDocument]).to eq('default')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when the value is 'updateLookup'" do
|
82
|
+
|
83
|
+
let(:options) do
|
84
|
+
{ full_document: 'updateLookup' }
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'sets the fullDocument value to updateLookup' do
|
88
|
+
expect(change_stream_document[:fullDocument]).to eq('updateLookup')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when full_document is not provided' do
|
94
|
+
|
95
|
+
it "defaults to use the 'default' value" do
|
96
|
+
expect(change_stream_document[:fullDocument]).to eq('default')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when resume_after is provided' do
|
101
|
+
|
102
|
+
let(:options) do
|
103
|
+
{ resume_after: sample_resume_token }
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'sets the resumeAfter value to the provided document' do
|
107
|
+
expect(change_stream_document[:resumeAfter]).to eq(sample_resume_token)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when max_await_time_ms is provided' do
|
112
|
+
|
113
|
+
let(:options) do
|
114
|
+
{ max_await_time_ms: 10 }
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'sets the maxTimeMS value to the provided document' do
|
118
|
+
expect(command_selector[:maxTimeMS]).to eq(10)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when batch_size is provided' do
|
123
|
+
|
124
|
+
let(:options) do
|
125
|
+
{ batch_size: 5 }
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'sets the batchSize value to the provided document' do
|
129
|
+
expect(command_selector[:cursor][:batchSize]).to eq(5)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when collation is provided' do
|
134
|
+
|
135
|
+
let(:options) do
|
136
|
+
{ 'collation' => { locale: 'en_US', strength: 2 } }
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'sets the collation value to the provided document' do
|
140
|
+
expect(error).to be_a(Mongo::Error::OperationFailure)
|
141
|
+
expect(error.message).to include('Only default collation is allowed')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when a changeStream operator is provided by the user as well' do
|
146
|
+
|
147
|
+
let(:pipeline) do
|
148
|
+
[ { '$changeStream' => { fullDocument: 'default' } }]
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'raises the error from the server' do
|
152
|
+
expect(error).to be_a(Mongo::Error::OperationFailure)
|
153
|
+
expect(error.message).to include('$changeStream is only valid as the first stage in a pipeline')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when the collection has a readConcern' do
|
158
|
+
|
159
|
+
let(:collection) do
|
160
|
+
authorized_collection.with(read_concern: { level: 'majority' })
|
161
|
+
end
|
162
|
+
|
163
|
+
let(:view) do
|
164
|
+
Mongo::Collection::View.new(collection, {}, options)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'uses the read concern of the collection' do
|
168
|
+
expect(command_selector[:readConcern]).to eq('level' => 'majority')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when no pipeline is supplied' do
|
173
|
+
|
174
|
+
it 'uses an empty pipeline' do
|
175
|
+
expect(command_selector[:pipeline][0].keys).to eq(['$changeStream'])
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when other pipeline operators are supplied' do
|
180
|
+
|
181
|
+
context 'when the other pipeline operators are supported' do
|
182
|
+
|
183
|
+
let(:pipeline) do
|
184
|
+
[ { '$project' => { '_id' => 0 }}]
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'uses the pipeline operators' do
|
188
|
+
expect(command_selector[:pipeline][1]).to eq(pipeline[0])
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when the other pipeline operators are not supported' do
|
193
|
+
|
194
|
+
let(:pipeline) do
|
195
|
+
[ { '$unwind' => '$test' }]
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'sends the pipeline to the server without a custom error' do
|
199
|
+
expect(change_stream).to be_a(Mongo::Collection::View::ChangeStream)
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'when the operation fails', if: sessions_enabled? && test_change_streams? do
|
203
|
+
|
204
|
+
let!(:before_last_use) do
|
205
|
+
session.instance_variable_get(:@server_session).last_use
|
206
|
+
end
|
207
|
+
|
208
|
+
let!(:before_operation_time) do
|
209
|
+
(session.instance_variable_get(:@operation_time) || 0)
|
210
|
+
end
|
211
|
+
|
212
|
+
let(:pipeline) do
|
213
|
+
[ { '$invalid' => '$test' }]
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:options) do
|
217
|
+
{ session: session }
|
218
|
+
end
|
219
|
+
|
220
|
+
let!(:operation_result) do
|
221
|
+
begin; change_stream; rescue => e; e; end
|
222
|
+
end
|
223
|
+
|
224
|
+
let(:session) do
|
225
|
+
client.start_session
|
226
|
+
end
|
227
|
+
|
228
|
+
let(:client) do
|
229
|
+
authorized_client
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'raises an error' do
|
233
|
+
expect(operation_result.class).to eq(Mongo::Error::OperationFailure)
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'updates the last use value' do
|
237
|
+
expect(session.instance_variable_get(:@server_session).last_use).not_to eq(before_last_use)
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'updates the operation time value' do
|
241
|
+
expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'when the initial batch is empty' do
|
248
|
+
|
249
|
+
before do
|
250
|
+
change_stream
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'does not close the cursor' do
|
254
|
+
expect(cursor).to be_a(Mongo::Cursor)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'when provided a session', if: sessions_enabled? && test_change_streams? do
|
259
|
+
|
260
|
+
let(:options) do
|
261
|
+
{ session: session }
|
262
|
+
end
|
263
|
+
|
264
|
+
let(:operation) do
|
265
|
+
change_stream
|
266
|
+
authorized_collection.insert_one(a: 1)
|
267
|
+
change_stream.to_enum.next
|
268
|
+
end
|
269
|
+
|
270
|
+
let(:client) do
|
271
|
+
authorized_client
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'when the session is created from the same client used for the operation' do
|
275
|
+
|
276
|
+
let(:session) do
|
277
|
+
client.start_session
|
278
|
+
end
|
279
|
+
|
280
|
+
let(:server_session) do
|
281
|
+
session.instance_variable_get(:@server_session)
|
282
|
+
end
|
283
|
+
|
284
|
+
let!(:before_last_use) do
|
285
|
+
server_session.last_use
|
286
|
+
end
|
287
|
+
|
288
|
+
let!(:before_operation_time) do
|
289
|
+
(session.instance_variable_get(:@operation_time) || 0)
|
290
|
+
end
|
291
|
+
|
292
|
+
let!(:operation_result) do
|
293
|
+
operation
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'updates the last use value' do
|
297
|
+
expect(server_session.last_use).not_to eq(before_last_use)
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'updates the operation time value' do
|
301
|
+
expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'does not close the session when the operation completes' do
|
305
|
+
expect(session.ended?).to be(false)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'when a session from another client is provided' do
|
310
|
+
|
311
|
+
let(:session) do
|
312
|
+
client.start_session
|
313
|
+
end
|
314
|
+
|
315
|
+
let(:client) do
|
316
|
+
authorized_client.with(read: { mode: :secondary })
|
317
|
+
end
|
318
|
+
|
319
|
+
let(:operation_result) do
|
320
|
+
operation
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'raises an exception' do
|
324
|
+
expect {
|
325
|
+
operation_result
|
326
|
+
}.to raise_exception(Mongo::Error::InvalidSession)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context 'when the session is ended before it is used' do
|
331
|
+
|
332
|
+
let(:session) do
|
333
|
+
client.start_session
|
334
|
+
end
|
335
|
+
|
336
|
+
before do
|
337
|
+
session.end_session
|
338
|
+
end
|
339
|
+
|
340
|
+
let(:operation_result) do
|
341
|
+
operation
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'raises an exception' do
|
345
|
+
expect {
|
346
|
+
operation_result
|
347
|
+
}.to raise_exception(Mongo::Error::InvalidSession)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe '#close' do
|
354
|
+
|
355
|
+
context 'when documents have not been retrieved and the stream is closed' do
|
356
|
+
|
357
|
+
before do
|
358
|
+
expect(cursor).to receive(:kill_cursors)
|
359
|
+
change_stream.close
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'closes the cursor' do
|
363
|
+
expect(change_stream.instance_variable_get(:@cursor)).to be(nil)
|
364
|
+
expect(change_stream.closed?).to be(true)
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'raises an error when the stream is attempted to be iterated' do
|
368
|
+
expect {
|
369
|
+
change_stream.to_enum.next
|
370
|
+
}.to raise_exception(StopIteration)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'when some documents have been retrieved and the stream is closed before sending getmore' do
|
375
|
+
|
376
|
+
before do
|
377
|
+
change_stream
|
378
|
+
authorized_collection.insert_one(a: 1)
|
379
|
+
enum.next
|
380
|
+
change_stream.close
|
381
|
+
end
|
382
|
+
|
383
|
+
let(:enum) do
|
384
|
+
change_stream.to_enum
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'raises an error' do
|
388
|
+
expect {
|
389
|
+
enum.next
|
390
|
+
}.to raise_exception(StopIteration)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe '#closed?' do
|
396
|
+
|
397
|
+
context 'when the change stream has not been closed' do
|
398
|
+
|
399
|
+
it 'returns false' do
|
400
|
+
expect(change_stream.closed?).to be(false)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
context 'when the change stream has been closed' do
|
405
|
+
|
406
|
+
before do
|
407
|
+
change_stream.close
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'returns false' do
|
411
|
+
expect(change_stream.closed?).to be(true)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
context 'when the first response does not contain the resume token' do
|
417
|
+
|
418
|
+
let(:pipeline) do
|
419
|
+
[ { '$project' => { _id: 0 } }]
|
420
|
+
end
|
421
|
+
|
422
|
+
before do
|
423
|
+
change_stream
|
424
|
+
authorized_collection.insert_one(a: 1)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'raises an exception and closes the cursor' do
|
428
|
+
expect(cursor).to receive(:kill_cursors).and_call_original
|
429
|
+
expect {
|
430
|
+
change_stream.to_enum.next
|
431
|
+
}.to raise_exception(Mongo::Error::MissingResumeToken)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context 'when an error is encountered the first time the command is run' do
|
436
|
+
|
437
|
+
let(:primary_socket) do
|
438
|
+
primary = authorized_collection.client.cluster.servers.find { |s| s.primary? }
|
439
|
+
connection = primary.pool.checkout
|
440
|
+
primary.pool.checkin(connection)
|
441
|
+
connection.send(:socket)
|
442
|
+
end
|
443
|
+
|
444
|
+
context 'when the error is a resumable error' do
|
445
|
+
|
446
|
+
shared_examples_for 'a resumable change stream' do
|
447
|
+
|
448
|
+
before do
|
449
|
+
expect(primary_socket).to receive(:write).and_raise(error).once
|
450
|
+
expect(view.send(:server_selector)).to receive(:select_server).twice.and_call_original
|
451
|
+
change_stream
|
452
|
+
authorized_collection.insert_one(a: 1)
|
453
|
+
end
|
454
|
+
|
455
|
+
let(:document) do
|
456
|
+
change_stream.to_enum.next
|
457
|
+
end
|
458
|
+
|
459
|
+
it 'runs the command again while using the same read preference and caches the resume token' do
|
460
|
+
expect(document[:fullDocument][:a]).to eq(1)
|
461
|
+
expect(change_stream_document[:resumeAfter]).to eq(document[:_id])
|
462
|
+
end
|
463
|
+
|
464
|
+
context 'when provided a session' do
|
465
|
+
|
466
|
+
let(:options) do
|
467
|
+
{ session: session}
|
468
|
+
end
|
469
|
+
|
470
|
+
let(:session) do
|
471
|
+
authorized_client.start_session
|
472
|
+
end
|
473
|
+
|
474
|
+
before do
|
475
|
+
change_stream.to_enum.next
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'does not close the session' do
|
479
|
+
expect(session.ended?).to be(false)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
context 'when the error is a SocketError' do
|
485
|
+
|
486
|
+
let(:error) do
|
487
|
+
Mongo::Error::SocketError
|
488
|
+
end
|
489
|
+
|
490
|
+
it_behaves_like 'a resumable change stream'
|
491
|
+
end
|
492
|
+
|
493
|
+
context 'when the error is a SocketTimeoutError' do
|
494
|
+
|
495
|
+
let(:error) do
|
496
|
+
Mongo::Error::SocketTimeoutError
|
497
|
+
end
|
498
|
+
|
499
|
+
it_behaves_like 'a resumable change stream'
|
500
|
+
end
|
501
|
+
|
502
|
+
context "when the error is a 'not master' error" do
|
503
|
+
|
504
|
+
let(:error) do
|
505
|
+
Mongo::Error::OperationFailure.new('not master')
|
506
|
+
end
|
507
|
+
|
508
|
+
it_behaves_like 'a resumable change stream'
|
509
|
+
end
|
510
|
+
|
511
|
+
context "when the error is a 'cursor not found (43)' error" do
|
512
|
+
|
513
|
+
let(:error) do
|
514
|
+
Mongo::Error::OperationFailure.new('cursor not found (43)')
|
515
|
+
end
|
516
|
+
|
517
|
+
it_behaves_like 'a resumable change stream'
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
context 'when the error is another server error' do
|
522
|
+
|
523
|
+
before do
|
524
|
+
expect(primary_socket).to receive(:write).and_raise(Mongo::Error::OperationFailure)
|
525
|
+
#expect twice because of kill_cursors in after block
|
526
|
+
expect(view.send(:server_selector)).to receive(:select_server).twice.and_call_original
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'does not run the command again and instead raises the error' do
|
530
|
+
expect {
|
531
|
+
change_stream
|
532
|
+
}.to raise_exception(Mongo::Error::OperationFailure)
|
533
|
+
end
|
534
|
+
|
535
|
+
context 'when provided a session' do
|
536
|
+
|
537
|
+
let(:options) do
|
538
|
+
{ session: session}
|
539
|
+
end
|
540
|
+
|
541
|
+
let(:session) do
|
542
|
+
authorized_client.start_session
|
543
|
+
end
|
544
|
+
|
545
|
+
before do
|
546
|
+
begin; change_stream; rescue; end
|
547
|
+
end
|
548
|
+
|
549
|
+
it 'does not close the session' do
|
550
|
+
expect(session.ended?).to be(false)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
context 'when a server error is encountered during a getmore' do
|
557
|
+
|
558
|
+
context 'when the error is a resumable error' do
|
559
|
+
|
560
|
+
shared_examples_for 'a change stream that encounters an error from a getmore' do
|
561
|
+
|
562
|
+
before do
|
563
|
+
change_stream
|
564
|
+
authorized_collection.insert_one(a: 1)
|
565
|
+
enum.next
|
566
|
+
authorized_collection.insert_one(a: 2)
|
567
|
+
expect(cursor).to receive(:get_more).once.and_raise(error)
|
568
|
+
expect(cursor).to receive(:kill_cursors).and_call_original
|
569
|
+
expect(Mongo::Operation::Commands::Aggregate).to receive(:new).and_call_original
|
570
|
+
end
|
571
|
+
|
572
|
+
let(:enum) do
|
573
|
+
change_stream.to_enum
|
574
|
+
end
|
575
|
+
|
576
|
+
let(:document) do
|
577
|
+
enum.next
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'runs the command again while using the same read preference and caching the resume token' do
|
581
|
+
expect(document[:fullDocument][:a]).to eq(2)
|
582
|
+
expect(change_stream_document[:resumeAfter]).to eq(document[:_id])
|
583
|
+
end
|
584
|
+
|
585
|
+
context 'when provided a session' do
|
586
|
+
|
587
|
+
let(:options) do
|
588
|
+
{ session: session}
|
589
|
+
end
|
590
|
+
|
591
|
+
let(:session) do
|
592
|
+
authorized_client.start_session
|
593
|
+
end
|
594
|
+
|
595
|
+
before do
|
596
|
+
enum.next
|
597
|
+
end
|
598
|
+
|
599
|
+
it 'does not close the session' do
|
600
|
+
expect(session.ended?).to be(false)
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
context 'when the error is a SocketError' do
|
606
|
+
|
607
|
+
let(:error) do
|
608
|
+
Mongo::Error::SocketError
|
609
|
+
end
|
610
|
+
|
611
|
+
it_behaves_like 'a change stream that encounters an error from a getmore'
|
612
|
+
end
|
613
|
+
|
614
|
+
context 'when the error is a SocketTimeoutError' do
|
615
|
+
|
616
|
+
let(:error) do
|
617
|
+
Mongo::Error::SocketTimeoutError
|
618
|
+
end
|
619
|
+
|
620
|
+
it_behaves_like 'a change stream that encounters an error from a getmore'
|
621
|
+
end
|
622
|
+
|
623
|
+
context "when the error is a not 'master error'" do
|
624
|
+
|
625
|
+
let(:error) do
|
626
|
+
Mongo::Error::OperationFailure.new('not master')
|
627
|
+
end
|
628
|
+
|
629
|
+
it_behaves_like 'a change stream that encounters an error from a getmore'
|
630
|
+
end
|
631
|
+
|
632
|
+
context "when the error is a not 'cursor not found error'" do
|
633
|
+
|
634
|
+
let(:error) do
|
635
|
+
Mongo::Error::OperationFailure.new('cursor not found (43)')
|
636
|
+
end
|
637
|
+
|
638
|
+
it_behaves_like 'a change stream that encounters an error from a getmore'
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
context 'when the error is another server error' do
|
643
|
+
|
644
|
+
before do
|
645
|
+
change_stream
|
646
|
+
authorized_collection.insert_one(a: 1)
|
647
|
+
enum.next
|
648
|
+
authorized_collection.insert_one(a: 2)
|
649
|
+
expect(cursor).to receive(:get_more).and_raise(Mongo::Error::OperationFailure)
|
650
|
+
expect(cursor).to receive(:kill_cursors).and_call_original
|
651
|
+
expect(Mongo::Operation::Commands::Aggregate).not_to receive(:new)
|
652
|
+
end
|
653
|
+
|
654
|
+
let(:enum) do
|
655
|
+
change_stream.to_enum
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'does not run the command again and instead raises the error' do
|
659
|
+
expect {
|
660
|
+
enum.next
|
661
|
+
}.to raise_exception(Mongo::Error::OperationFailure)
|
662
|
+
end
|
663
|
+
|
664
|
+
context 'when provided a session' do
|
665
|
+
|
666
|
+
let(:options) do
|
667
|
+
{ session: session}
|
668
|
+
end
|
669
|
+
|
670
|
+
let(:session) do
|
671
|
+
authorized_client.start_session
|
672
|
+
end
|
673
|
+
|
674
|
+
before do
|
675
|
+
begin; enum.next; rescue; end
|
676
|
+
end
|
677
|
+
|
678
|
+
it 'does not close the session' do
|
679
|
+
expect(session.ended?).to be(false)
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
context 'when a server error is encountered during the command following an error during getmore' do
|
686
|
+
|
687
|
+
context 'when the error is a resumable error' do
|
688
|
+
|
689
|
+
shared_examples_for 'a change stream that sent getmores, that then encounters an error when resuming' do
|
690
|
+
|
691
|
+
before do
|
692
|
+
change_stream
|
693
|
+
authorized_collection.insert_one(a: 1)
|
694
|
+
enum.next
|
695
|
+
authorized_collection.insert_one(a: 2)
|
696
|
+
expect(cursor).to receive(:get_more).and_raise(error)
|
697
|
+
expect(cursor).to receive(:kill_cursors).and_call_original
|
698
|
+
expect(change_stream).to receive(:send_initial_query).and_raise(error).once.ordered
|
699
|
+
end
|
700
|
+
|
701
|
+
let(:enum) do
|
702
|
+
change_stream.to_enum
|
703
|
+
end
|
704
|
+
|
705
|
+
let(:document) do
|
706
|
+
enum.next
|
707
|
+
end
|
708
|
+
|
709
|
+
it 'raises the error' do
|
710
|
+
expect {
|
711
|
+
document
|
712
|
+
}.to raise_exception(error)
|
713
|
+
end
|
714
|
+
|
715
|
+
context 'when provided a session' do
|
716
|
+
|
717
|
+
let(:options) do
|
718
|
+
{ session: session}
|
719
|
+
end
|
720
|
+
|
721
|
+
let(:session) do
|
722
|
+
authorized_client.start_session
|
723
|
+
end
|
724
|
+
|
725
|
+
before do
|
726
|
+
begin; document; rescue; end
|
727
|
+
end
|
728
|
+
|
729
|
+
it 'does not close the session' do
|
730
|
+
expect(session.ended?).to be(false)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
context 'when the error is a SocketError' do
|
736
|
+
|
737
|
+
let(:error) do
|
738
|
+
Mongo::Error::SocketError
|
739
|
+
end
|
740
|
+
|
741
|
+
it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
|
742
|
+
end
|
743
|
+
|
744
|
+
context 'when the error is a SocketTimeoutError' do
|
745
|
+
|
746
|
+
let(:error) do
|
747
|
+
Mongo::Error::SocketTimeoutError
|
748
|
+
end
|
749
|
+
|
750
|
+
it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
|
751
|
+
end
|
752
|
+
|
753
|
+
context "when the error is a 'not master error'" do
|
754
|
+
|
755
|
+
let(:error) do
|
756
|
+
Mongo::Error::OperationFailure.new('not master')
|
757
|
+
end
|
758
|
+
|
759
|
+
it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
|
760
|
+
end
|
761
|
+
|
762
|
+
context "when the error is a not 'cursor not found error'" do
|
763
|
+
|
764
|
+
let(:error) do
|
765
|
+
Mongo::Error::OperationFailure.new('cursor not found (43)')
|
766
|
+
end
|
767
|
+
|
768
|
+
it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
context 'when the error is another server error' do
|
773
|
+
|
774
|
+
before do
|
775
|
+
change_stream
|
776
|
+
authorized_collection.insert_one(a: 1)
|
777
|
+
enum.next
|
778
|
+
authorized_collection.insert_one(a: 2)
|
779
|
+
expect(cursor).to receive(:get_more).and_raise(Mongo::Error::OperationFailure.new('not master'))
|
780
|
+
expect(cursor).to receive(:kill_cursors).and_call_original
|
781
|
+
expect(change_stream).to receive(:send_initial_query).and_raise(Mongo::Error::OperationFailure).once.ordered
|
782
|
+
end
|
783
|
+
|
784
|
+
let(:enum) do
|
785
|
+
change_stream.to_enum
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'does not run the command again and instead raises the error' do
|
789
|
+
expect {
|
790
|
+
enum.next
|
791
|
+
}.to raise_exception(Mongo::Error::OperationFailure)
|
792
|
+
end
|
793
|
+
|
794
|
+
context 'when provided a session' do
|
795
|
+
|
796
|
+
let(:options) do
|
797
|
+
{ session: session}
|
798
|
+
end
|
799
|
+
|
800
|
+
let(:session) do
|
801
|
+
authorized_client.start_session
|
802
|
+
end
|
803
|
+
|
804
|
+
before do
|
805
|
+
begin; enum.next; rescue; end
|
806
|
+
end
|
807
|
+
|
808
|
+
it 'does not close the session' do
|
809
|
+
expect(session.ended?).to be(false)
|
810
|
+
end
|
811
|
+
end
|
812
|
+
end
|
813
|
+
end
|
814
|
+
end
|