mongo 2.14.1 → 2.15.0.alpha
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 +0 -0
- data/README.md +4 -1
- data/Rakefile +8 -15
- data/lib/mongo/auth/aws/conversation.rb +1 -4
- data/lib/mongo/auth/base.rb +13 -7
- data/lib/mongo/auth/conversation_base.rb +32 -0
- data/lib/mongo/auth/cr/conversation.rb +6 -29
- data/lib/mongo/auth/gssapi/conversation.rb +4 -15
- data/lib/mongo/auth/ldap/conversation.rb +3 -14
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
- data/lib/mongo/auth/scram_conversation_base.rb +7 -34
- data/lib/mongo/auth/user/view.rb +16 -9
- data/lib/mongo/auth/x509/conversation.rb +4 -25
- data/lib/mongo/bulk_write.rb +21 -18
- data/lib/mongo/client.rb +82 -6
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
- data/lib/mongo/cluster.rb +19 -2
- data/lib/mongo/collection/view/aggregation.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +1 -1
- data/lib/mongo/collection/view/iterable.rb +7 -17
- data/lib/mongo/collection/view/map_reduce.rb +2 -2
- data/lib/mongo/collection/view/readable.rb +42 -20
- data/lib/mongo/collection/view/writable.rb +14 -14
- data/lib/mongo/collection.rb +6 -6
- data/lib/mongo/cursor.rb +2 -12
- data/lib/mongo/database/view.rb +1 -1
- data/lib/mongo/database.rb +8 -3
- data/lib/mongo/error/bulk_write_error.rb +17 -3
- data/lib/mongo/error/internal_driver_error.rb +22 -0
- data/lib/mongo/error/operation_failure.rb +21 -2
- data/lib/mongo/error/parser.rb +65 -12
- data/lib/mongo/error/server_api_conflict.rb +23 -0
- data/lib/mongo/error/server_api_not_supported.rb +24 -0
- data/lib/mongo/error/unmet_dependency.rb +21 -0
- data/lib/mongo/error.rb +9 -1
- data/lib/mongo/index/view.rb +21 -11
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
- data/lib/mongo/monitoring.rb +13 -4
- data/lib/mongo/operation/collections_info/command.rb +2 -2
- data/lib/mongo/operation/collections_info.rb +18 -1
- data/lib/mongo/operation/context.rb +99 -0
- data/lib/mongo/operation/indexes.rb +15 -1
- data/lib/mongo/operation/insert/command.rb +2 -2
- data/lib/mongo/operation/insert/legacy.rb +2 -2
- data/lib/mongo/operation/insert/op_msg.rb +2 -2
- data/lib/mongo/operation/list_collections/result.rb +4 -1
- data/lib/mongo/operation/parallel_scan/command.rb +2 -1
- data/lib/mongo/operation/result.rb +2 -0
- data/lib/mongo/operation/shared/executable.rb +24 -14
- data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
- data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
- data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
- data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
- data/lib/mongo/operation/shared/read_preference_supported.rb +36 -38
- data/lib/mongo/operation/shared/response_handling.rb +23 -23
- data/lib/mongo/operation/shared/sessions_supported.rb +15 -5
- data/lib/mongo/operation/shared/write.rb +8 -18
- data/lib/mongo/operation.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +51 -5
- data/lib/mongo/protocol/message.rb +20 -2
- data/lib/mongo/protocol/msg.rb +38 -13
- data/lib/mongo/protocol/query.rb +11 -11
- data/lib/mongo/query_cache.rb +30 -0
- data/lib/mongo/retryable.rb +1 -1
- data/lib/mongo/server/app_metadata.rb +52 -18
- data/lib/mongo/server/connection.rb +5 -0
- data/lib/mongo/server/connection_base.rb +13 -10
- data/lib/mongo/server/connection_pool.rb +6 -2
- data/lib/mongo/server/description/features.rb +9 -8
- data/lib/mongo/server/description.rb +4 -0
- data/lib/mongo/server/monitor/app_metadata.rb +1 -1
- data/lib/mongo/server/monitor/connection.rb +9 -10
- data/lib/mongo/server/monitor.rb +20 -1
- data/lib/mongo/server/pending_connection.rb +24 -6
- data/lib/mongo/server/push_monitor.rb +11 -1
- data/lib/mongo/server.rb +7 -1
- data/lib/mongo/server_selector/secondary_preferred.rb +7 -2
- data/lib/mongo/session/session_pool.rb +4 -2
- data/lib/mongo/session.rb +2 -2
- data/lib/mongo/socket/ssl.rb +8 -0
- data/lib/mongo/socket.rb +29 -4
- data/lib/mongo/uri/options_mapper.rb +38 -0
- data/lib/mongo/utils.rb +15 -0
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +23 -0
- data/spec/README.md +24 -1
- data/spec/integration/auth_spec.rb +25 -15
- data/spec/integration/bulk_write_error_message_spec.rb +41 -0
- data/spec/integration/change_stream_spec.rb +4 -4
- data/spec/integration/command_monitoring_spec.rb +2 -2
- data/spec/integration/connection_spec.rb +2 -0
- data/spec/integration/docs_examples_spec.rb +8 -1
- data/spec/integration/fork_reconnect_spec.rb +4 -1
- data/spec/integration/ocsp_verifier_spec.rb +13 -7
- data/spec/integration/operation_failure_code_spec.rb +1 -1
- data/spec/integration/operation_failure_message_spec.rb +90 -0
- data/spec/integration/query_cache_spec.rb +0 -45
- data/spec/integration/reconnect_spec.rb +1 -1
- data/spec/integration/snappy_compression_spec.rb +25 -0
- data/spec/integration/srv_monitoring_spec.rb +1 -1
- data/spec/integration/transactions_examples_spec.rb +6 -0
- data/spec/integration/zlib_compression_spec.rb +1 -1
- data/spec/integration/zstd_compression_spec.rb +26 -0
- data/spec/lite_spec_helper.rb +7 -1
- data/spec/mongo/address_spec.rb +15 -11
- data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
- data/spec/mongo/auth/ldap_spec.rb +5 -1
- data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
- data/spec/mongo/auth/scram_spec.rb +1 -1
- data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
- data/spec/mongo/client_construction_spec.rb +207 -33
- data/spec/mongo/client_spec.rb +17 -0
- data/spec/mongo/cluster_spec.rb +1 -0
- data/spec/mongo/collection/view/explainable_spec.rb +1 -1
- data/spec/mongo/collection/view/readable_spec.rb +33 -19
- data/spec/mongo/collection_crud_spec.rb +4357 -0
- data/spec/mongo/collection_ddl_spec.rb +534 -0
- data/spec/mongo/collection_spec.rb +5 -4859
- data/spec/mongo/database_spec.rb +66 -4
- data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
- data/spec/mongo/error/parser_spec.rb +37 -6
- data/spec/mongo/index/view_spec.rb +4 -0
- data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
- data/spec/mongo/operation/aggregate_spec.rb +2 -1
- data/spec/mongo/operation/collections_info_spec.rb +4 -1
- data/spec/mongo/operation/command_spec.rb +6 -3
- data/spec/mongo/operation/create_index_spec.rb +6 -3
- data/spec/mongo/operation/create_user_spec.rb +6 -3
- data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
- data/spec/mongo/operation/delete_spec.rb +11 -7
- data/spec/mongo/operation/drop_index_spec.rb +6 -2
- data/spec/mongo/operation/find/legacy_spec.rb +3 -1
- data/spec/mongo/operation/get_more_spec.rb +3 -1
- data/spec/mongo/operation/indexes_spec.rb +5 -1
- data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
- data/spec/mongo/operation/insert_spec.rb +15 -12
- data/spec/mongo/operation/map_reduce_spec.rb +5 -2
- data/spec/mongo/operation/read_preference_legacy_spec.rb +19 -9
- data/spec/mongo/operation/read_preference_op_msg_spec.rb +3 -3
- data/spec/mongo/operation/remove_user_spec.rb +6 -3
- data/spec/mongo/operation/result_spec.rb +1 -1
- data/spec/mongo/operation/update/bulk_spec.rb +9 -6
- data/spec/mongo/operation/update_spec.rb +10 -7
- data/spec/mongo/operation/update_user_spec.rb +4 -1
- data/spec/mongo/protocol/compressed_spec.rb +26 -12
- data/spec/mongo/query_cache_middleware_spec.rb +55 -0
- data/spec/mongo/retryable_spec.rb +3 -2
- data/spec/mongo/server/app_metadata_shared.rb +7 -33
- data/spec/mongo/server/app_metadata_spec.rb +2 -0
- data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
- data/spec/mongo/server/connection_pool_spec.rb +1 -1
- data/spec/mongo/server/connection_spec.rb +24 -17
- data/spec/mongo/server/monitor/connection_spec.rb +17 -7
- data/spec/mongo/server/monitor_spec.rb +9 -1
- data/spec/mongo/server_selector/secondary_preferred_spec.rb +6 -6
- data/spec/mongo/server_spec.rb +15 -2
- data/spec/mongo/socket/ssl_spec.rb +40 -0
- data/spec/mongo/socket_spec.rb +2 -2
- data/spec/mongo/tls_context_hooks_spec.rb +37 -0
- data/spec/runners/connection_string.rb +0 -4
- data/spec/runners/crud/requirement.rb +40 -3
- data/spec/runners/crud/verifier.rb +8 -0
- data/spec/runners/transactions/operation.rb +1 -1
- data/spec/runners/transactions/test.rb +1 -0
- data/spec/runners/unified/assertions.rb +249 -0
- data/spec/runners/unified/change_stream_operations.rb +26 -0
- data/spec/runners/unified/crud_operations.rb +199 -0
- data/spec/runners/unified/ddl_operations.rb +96 -0
- data/spec/runners/unified/entity_map.rb +39 -0
- data/spec/runners/unified/error.rb +25 -0
- data/spec/runners/unified/event_subscriber.rb +91 -0
- data/spec/runners/unified/exceptions.rb +21 -0
- data/spec/runners/unified/grid_fs_operations.rb +55 -0
- data/spec/runners/unified/support_operations.rb +250 -0
- data/spec/runners/unified/test.rb +393 -0
- data/spec/runners/unified/test_group.rb +28 -0
- data/spec/runners/unified/using_hash.rb +31 -0
- data/spec/runners/unified.rb +96 -0
- data/spec/shared/lib/mrss/cluster_config.rb +0 -3
- data/spec/shared/lib/mrss/docker_runner.rb +0 -3
- data/spec/shared/lib/mrss/lite_constraints.rb +0 -16
- data/spec/shared/lib/mrss/server_version_registry.rb +0 -3
- data/spec/shared/lib/mrss/spec_organizer.rb +0 -3
- data/spec/shared/shlib/server.sh +1 -1
- data/spec/spec_helper.rb +4 -1
- data/spec/spec_tests/crud_unified_spec.rb +10 -0
- data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
- data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
- data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
- data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
- data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
- data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
- data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
- data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
- data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
- data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
- data/spec/spec_tests/unified_spec.rb +15 -0
- data/spec/spec_tests/uri_options_spec.rb +16 -0
- data/spec/spec_tests/versioned_api_spec.rb +10 -0
- data/spec/support/client_registry.rb +4 -8
- data/spec/support/client_registry_macros.rb +4 -4
- data/spec/support/common_shortcuts.rb +15 -1
- data/spec/support/shared/session.rb +2 -2
- data/spec/support/spec_config.rb +42 -11
- data/spec/support/utils.rb +64 -3
- data.tar.gz.sig +0 -0
- metadata +1005 -915
- metadata.gz.sig +0 -0
- data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
- data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
- data/spec/integration/secondary_reads_spec.rb +0 -102
- data/spec/support/cluster_config.rb +0 -207
@@ -0,0 +1,249 @@
|
|
1
|
+
module Unified
|
2
|
+
|
3
|
+
module Assertions
|
4
|
+
|
5
|
+
def assert_result_matches(actual, expected)
|
6
|
+
if Hash === expected
|
7
|
+
use_all(expected, 'expected result', expected) do |expected|
|
8
|
+
%w(deleted inserted matched modified upserted).each do |k|
|
9
|
+
if count = expected.use("#{k}Count")
|
10
|
+
if Hash === count || count > 0
|
11
|
+
actual_count = case actual
|
12
|
+
when Mongo::BulkWrite::Result, Mongo::Operation::Delete::Result
|
13
|
+
actual.send("#{k}_count")
|
14
|
+
else
|
15
|
+
actual["n_#{k}"]
|
16
|
+
end
|
17
|
+
assert_value_matches(actual_count, count, "#{k} count")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
%w(inserted upserted).each do |k|
|
22
|
+
expected_v = expected.use("#{k}Ids")
|
23
|
+
next unless expected_v
|
24
|
+
actual_v = case actual
|
25
|
+
when Mongo::BulkWrite::Result, Mongo::Operation::Update::Result
|
26
|
+
# Ruby driver returns inserted ids as an array of ids.
|
27
|
+
# The yaml file specifies them as a map from operation.
|
28
|
+
if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches)
|
29
|
+
expected_v = expected_v.values.first.values
|
30
|
+
elsif Hash === expected_v
|
31
|
+
expected_v = expected_v.values
|
32
|
+
end
|
33
|
+
actual.send("#{k}_ids")
|
34
|
+
else
|
35
|
+
actual["#{k}_ids"]
|
36
|
+
end
|
37
|
+
if expected_v
|
38
|
+
if expected_v.empty?
|
39
|
+
if actual_v && !actual_v.empty?
|
40
|
+
raise Error::ResultMismatch, "Actual not empty"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
if actual_v != expected_v
|
44
|
+
raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_matches(actual, expected, 'result')
|
51
|
+
expected.clear
|
52
|
+
end
|
53
|
+
else
|
54
|
+
assert_matches(actual, expected, 'result')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def assert_outcome
|
59
|
+
return unless outcome
|
60
|
+
|
61
|
+
client = ClientRegistry.instance.global_client('authorized')
|
62
|
+
outcome.each do |spec|
|
63
|
+
spec = UsingHash[spec]
|
64
|
+
collection = client.use(spec.use!('databaseName'))[spec.use!('collectionName')]
|
65
|
+
expected_docs = spec.use!('documents')
|
66
|
+
actual_docs = collection.find({}, order: :_id).to_a
|
67
|
+
assert_documents_match(actual_docs, expected_docs)
|
68
|
+
unless spec.empty?
|
69
|
+
raise NotImplementedError, "Unhandled keys: #{spec}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def assert_documents_match(actual, expected)
|
75
|
+
unless actual.length == expected.length
|
76
|
+
raise Error::ResultMismatch, "Unexpected number of documents: expected #{expected.length}, actual #{actual.length}"
|
77
|
+
end
|
78
|
+
|
79
|
+
actual.each_with_index do |document, index|
|
80
|
+
assert_matches(document, expected[index], "document ##{index}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def assert_document_matches(actual, expected, msg)
|
85
|
+
unless actual == expected
|
86
|
+
p actual
|
87
|
+
p expected
|
88
|
+
raise Error::ResultMismatch, "#{msg} does not match"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def assert_events
|
93
|
+
return unless @expected_events
|
94
|
+
@expected_events.each do |spec|
|
95
|
+
spec = UsingHash[spec]
|
96
|
+
client_id = spec.use!('client')
|
97
|
+
client = entities.get(:client, client_id)
|
98
|
+
subscriber = @subscribers.fetch(client)
|
99
|
+
expected_events = spec.use!('events')
|
100
|
+
actual_events = subscriber.wanted_events
|
101
|
+
unless actual_events.length == expected_events.length
|
102
|
+
raise Error::ResultMismatch, "Event count mismatch: expected #{expected_events.length}, actual #{actual_events.length}\nExpected: #{expected_events}\nActual: #{actual_events}"
|
103
|
+
end
|
104
|
+
expected_events.each_with_index do |event, i|
|
105
|
+
assert_event_matches(actual_events[i], event)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_event_matches(actual, expected)
|
111
|
+
assert_eq(expected.keys.length, 1, "Expected event must have one key: #{expected}")
|
112
|
+
expected_name, spec = expected.first
|
113
|
+
spec = UsingHash[spec]
|
114
|
+
expected_name = expected_name.sub(/Event$/, '').sub(/^(.)/) { $1.upcase }
|
115
|
+
assert_eq(actual.class.name.sub(/.*::/, ''), expected_name, 'Event name does not match')
|
116
|
+
if db_name = spec.use('databaseName')
|
117
|
+
assert_eq(actual.database_name, db_name, 'Database names differ')
|
118
|
+
end
|
119
|
+
if command_name = spec.use('commandName')
|
120
|
+
assert_eq(actual.command_name, command_name, 'Command names differ')
|
121
|
+
end
|
122
|
+
if command = spec.use('command')
|
123
|
+
assert_matches(actual.command, command, 'Commands differ')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def assert_eq(actual, expected, msg)
|
128
|
+
unless expected == actual
|
129
|
+
raise Error::ResultMismatch, "#{msg}: expected #{expected}, actual #{actual}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def assert_matches(actual, expected, msg)
|
134
|
+
if actual.nil? && !expected.nil?
|
135
|
+
raise Error::ResultMismatch, "#{msg}: expected #{expected} but got nil"
|
136
|
+
end
|
137
|
+
|
138
|
+
case expected
|
139
|
+
when Array
|
140
|
+
unless Array === actual
|
141
|
+
raise Error::ResultMismatch, "Expected an array, found #{actual}"
|
142
|
+
end
|
143
|
+
unless actual.length == expected.length
|
144
|
+
raise Error::ResultMismatch, "Expected array of length #{expected.length}, found array of length #{actual.length}: #{actual}"
|
145
|
+
end
|
146
|
+
expected.each_with_index do |v, i|
|
147
|
+
assert_matches(actual[i], v, "#{msg}: index #{i}")
|
148
|
+
end
|
149
|
+
when Hash
|
150
|
+
if expected.keys == %w($$unsetOrMatches) && expected.values.first.keys == %w(insertedId)
|
151
|
+
actual_v = actual.inserted_id
|
152
|
+
expected_v = expected.values.first.values.first
|
153
|
+
assert_value_matches(actual_v, expected_v, 'inserted_id')
|
154
|
+
else
|
155
|
+
expected.each do |k, expected_v|
|
156
|
+
if k.start_with?('$$')
|
157
|
+
assert_value_matches(actual, expected, k)
|
158
|
+
else
|
159
|
+
actual_v = actual[k]
|
160
|
+
if Hash === expected_v && expected_v.length == 1 && expected_v.keys.first.start_with?('$$')
|
161
|
+
assert_value_matches(actual_v, expected_v, k)
|
162
|
+
else
|
163
|
+
assert_matches(actual_v, expected_v, "#{msg}: key #{k}")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
else
|
169
|
+
if Integer === expected && BSON::Int64 === actual
|
170
|
+
actual = actual.value
|
171
|
+
end
|
172
|
+
unless actual == expected
|
173
|
+
raise Error::ResultMismatch, "#{msg}: expected #{expected}, actual #{actual}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def assert_type(object, type)
|
179
|
+
ok = case type
|
180
|
+
when 'object'
|
181
|
+
Hash === object
|
182
|
+
when %w(int long)
|
183
|
+
Integer === object || BSON::Int32 === object || BSON::Int64 === object
|
184
|
+
when 'objectId'
|
185
|
+
BSON::ObjectId === object
|
186
|
+
when 'date'
|
187
|
+
Time === object
|
188
|
+
else
|
189
|
+
raise NotImplementedError, "Unhandled type #{type}"
|
190
|
+
end
|
191
|
+
unless ok
|
192
|
+
raise Error::ResultMismatch, "Object #{object} is not of type #{type}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def assert_value_matches(actual, expected, msg)
|
197
|
+
if Hash === expected && expected.keys.length == 1 &&
|
198
|
+
(operator = expected.keys.first).start_with?('$$')
|
199
|
+
then
|
200
|
+
expected_v = expected.values.first
|
201
|
+
case operator
|
202
|
+
when '$$unsetOrMatches'
|
203
|
+
if actual
|
204
|
+
unless actual == expected_v
|
205
|
+
raise Error::ResultMismatch, "Mismatch for #{msg}: expected #{expected}, have #{actual}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
when '$$matchesHexBytes'
|
209
|
+
expected_data = decode_hex_bytes(expected_v)
|
210
|
+
unless actual == expected_data
|
211
|
+
raise Error::ResultMismatch, "Hex bytes do not match"
|
212
|
+
end
|
213
|
+
when '$$exists'
|
214
|
+
case expected_v
|
215
|
+
when true
|
216
|
+
if actual.nil?
|
217
|
+
raise Error::ResultMismatch, "#{msg}: wanted value to exist, but it did not"
|
218
|
+
end
|
219
|
+
when false
|
220
|
+
if actual
|
221
|
+
raise Error::ResultMismatch, "#{msg}: wanted value to not exist, but it did"
|
222
|
+
end
|
223
|
+
else
|
224
|
+
raise NotImplementedError, "Bogus value #{expected_v}"
|
225
|
+
end
|
226
|
+
when '$$sessionLsid'
|
227
|
+
expected_session = entities.get(:session, expected_v)
|
228
|
+
# TODO - sessions do not expose server sessions after being ended
|
229
|
+
#unless actual_v == {'id' => expected_session.server_session.session_id.to_bson}
|
230
|
+
# raise Error::ResultMismatch, "Session does not match: wanted #{expected_session}, have #{actual_v}"
|
231
|
+
#end
|
232
|
+
when '$$type'
|
233
|
+
assert_type(actual, expected_v)
|
234
|
+
when '$$matchesEntity'
|
235
|
+
result = entities.get(:result, expected_v)
|
236
|
+
unless actual == result
|
237
|
+
raise Error::ResultMismatch, "Actual value #{actual} does not match entity #{expected_v} with value #{result}"
|
238
|
+
end
|
239
|
+
else
|
240
|
+
raise NotImplementedError, "Unknown operator #{operator}"
|
241
|
+
end
|
242
|
+
else
|
243
|
+
if actual != expected
|
244
|
+
raise Error::ResultMismatch, "Mismatch for #{msg}: expected #{expected}, have #{actual}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Unified
|
2
|
+
|
3
|
+
module ChangeStreamOperations
|
4
|
+
|
5
|
+
def create_change_stream(op)
|
6
|
+
object_id = op.use!('object')
|
7
|
+
object = entities.get_any(object_id)
|
8
|
+
use_arguments(op) do |args|
|
9
|
+
pipeline = args.use!('pipeline')
|
10
|
+
opts = {}
|
11
|
+
if batch_size = args.use('batchSize')
|
12
|
+
opts[:batch_size] = batch_size
|
13
|
+
end
|
14
|
+
cs = object.watch(pipeline, **opts)
|
15
|
+
name = op.use!('saveResultAsEntity')
|
16
|
+
entities.set(:change_stream, name, cs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def iterate_until_document_or_error(op)
|
21
|
+
object_id = op.use!('object')
|
22
|
+
object = entities.get(:change_stream, object_id)
|
23
|
+
object.to_enum.next
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module Unified
|
2
|
+
|
3
|
+
module CrudOperations
|
4
|
+
|
5
|
+
def find(op)
|
6
|
+
collection = entities.get(:collection, op.use!('object'))
|
7
|
+
use_arguments(op) do |args|
|
8
|
+
req = collection.find(args.use!('filter'))
|
9
|
+
if batch_size = args.use('batchSize')
|
10
|
+
req = req.batch_size(batch_size)
|
11
|
+
end
|
12
|
+
if sort = args.use('sort')
|
13
|
+
req = req.sort(sort)
|
14
|
+
end
|
15
|
+
if limit = args.use('limit')
|
16
|
+
req = req.limit(limit)
|
17
|
+
end
|
18
|
+
result = req.to_a
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def count_documents(op)
|
23
|
+
collection = entities.get(:collection, op.use!('object'))
|
24
|
+
use_arguments(op) do |args|
|
25
|
+
collection.find(args.use!('filter')).count_documents
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def estimated_document_count(op)
|
30
|
+
collection = entities.get(:collection, op.use!('object'))
|
31
|
+
use_arguments(op) do |args|
|
32
|
+
opts = {}
|
33
|
+
if max_time_ms = args.use('maxTimeMS')
|
34
|
+
opts[:max_time_ms] = max_time_ms
|
35
|
+
end
|
36
|
+
collection.estimated_document_count(**opts)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def distinct(op)
|
41
|
+
collection = entities.get(:collection, op.use!('object'))
|
42
|
+
use_arguments(op) do |args|
|
43
|
+
req = collection.find(args.use!('filter')).distinct(args.use!('fieldName'))
|
44
|
+
result = req.to_a
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_one_and_update(op)
|
49
|
+
collection = entities.get(:collection, op.use!('object'))
|
50
|
+
use_arguments(op) do |args|
|
51
|
+
filter = args.use!('filter')
|
52
|
+
update = args.use!('update')
|
53
|
+
opts = {}
|
54
|
+
if return_document = args.use('returnDocument')
|
55
|
+
opts[:return_document] = return_document.downcase.to_sym
|
56
|
+
end
|
57
|
+
collection.find_one_and_update(filter, update, **opts)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_one_and_replace(op)
|
62
|
+
collection = entities.get(:collection, op.use!('object'))
|
63
|
+
use_arguments(op) do |args|
|
64
|
+
filter = args.use!('filter')
|
65
|
+
update = args.use!('replacement')
|
66
|
+
collection.find_one_and_replace(filter, update)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_one_and_delete(op)
|
71
|
+
collection = entities.get(:collection, op.use!('object'))
|
72
|
+
use_arguments(op) do |args|
|
73
|
+
filter = args.use!('filter')
|
74
|
+
collection.find_one_and_delete(filter)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def insert_one(op)
|
79
|
+
collection = entities.get(:collection, op.use!('object'))
|
80
|
+
use_arguments(op) do |args|
|
81
|
+
opts = {}
|
82
|
+
if session = args.use('session')
|
83
|
+
opts[:session] = entities.get(:session, session)
|
84
|
+
end
|
85
|
+
collection.insert_one(args.use!('document'), **opts)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def insert_many(op)
|
90
|
+
collection = entities.get(:collection, op.use!('object'))
|
91
|
+
use_arguments(op) do |args|
|
92
|
+
options = {}
|
93
|
+
unless (ordered = args.use('ordered')).nil?
|
94
|
+
options[:ordered] = ordered
|
95
|
+
end
|
96
|
+
collection.insert_many(args.use!('documents'), **options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def update_one(op)
|
101
|
+
collection = entities.get(:collection, op.use!('object'))
|
102
|
+
use_arguments(op) do |args|
|
103
|
+
collection.update_one(args.use!('filter'), args.use!('update'))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_many(op)
|
108
|
+
collection = entities.get(:collection, op.use!('object'))
|
109
|
+
use_arguments(op) do |args|
|
110
|
+
collection.update_many(args.use!('filter'), args.use!('update'))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def replace_one(op)
|
115
|
+
collection = entities.get(:collection, op.use!('object'))
|
116
|
+
use_arguments(op) do |args|
|
117
|
+
collection.replace_one(
|
118
|
+
args.use!('filter'),
|
119
|
+
args.use!('replacement'),
|
120
|
+
upsert: args.use('upsert'),
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def delete_one(op)
|
126
|
+
collection = entities.get(:collection, op.use!('object'))
|
127
|
+
use_arguments(op) do |args|
|
128
|
+
collection.delete_one(args.use!('filter'))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def delete_many(op)
|
133
|
+
collection = entities.get(:collection, op.use!('object'))
|
134
|
+
use_arguments(op) do |args|
|
135
|
+
collection.delete_many(args.use!('filter'))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def bulk_write(op)
|
140
|
+
collection = entities.get(:collection, op.use!('object'))
|
141
|
+
use_arguments(op) do |args|
|
142
|
+
requests = args.use!('requests').map do |req|
|
143
|
+
convert_bulk_write_spec(req)
|
144
|
+
end
|
145
|
+
opts = {}
|
146
|
+
if ordered = args.use('ordered')
|
147
|
+
opts[:ordered] = true
|
148
|
+
end
|
149
|
+
collection.bulk_write(requests, **opts)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def convert_bulk_write_spec(spec)
|
156
|
+
unless spec.keys.length == 1
|
157
|
+
raise NotImplementedError, "Must have exactly one item"
|
158
|
+
end
|
159
|
+
op, spec = spec.first
|
160
|
+
spec = UsingHash[spec]
|
161
|
+
out = case op
|
162
|
+
when 'insertOne'
|
163
|
+
spec.use!('document')
|
164
|
+
when 'updateOne', 'updateMany'
|
165
|
+
{
|
166
|
+
filter: spec.use('filter'),
|
167
|
+
update: spec.use('update'),
|
168
|
+
upsert: spec.use('upsert'),
|
169
|
+
}
|
170
|
+
when 'replaceOne'
|
171
|
+
{
|
172
|
+
filter: spec.use('filter'),
|
173
|
+
replacement: spec.use('replacement'),
|
174
|
+
upsert: spec.use('upsert'),
|
175
|
+
}
|
176
|
+
when 'deleteOne', 'deleteMany'
|
177
|
+
{
|
178
|
+
filter: spec.use('filter'),
|
179
|
+
}
|
180
|
+
else
|
181
|
+
raise NotImplementedError, "Unknown operation #{op}"
|
182
|
+
end
|
183
|
+
unless spec.empty?
|
184
|
+
raise NotImplementedError, "Unhandled keys: #{spec}"
|
185
|
+
end
|
186
|
+
{Utils.underscore(op) =>out}
|
187
|
+
end
|
188
|
+
|
189
|
+
def aggregate(op)
|
190
|
+
obj = entities.get_any(op.use!('object'))
|
191
|
+
args = op.use!('arguments')
|
192
|
+
pipeline = args.use!('pipeline')
|
193
|
+
unless args.empty?
|
194
|
+
raise NotImplementedError, "Unhandled spec keys: #{test_spec}"
|
195
|
+
end
|
196
|
+
obj.aggregate(pipeline).to_a
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Unified
|
2
|
+
|
3
|
+
module DdlOperations
|
4
|
+
|
5
|
+
def list_databases(op)
|
6
|
+
client = entities.get(:client, op.use!('object'))
|
7
|
+
client.list_databases
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_collection(op)
|
11
|
+
database = entities.get(:database, op.use!('object'))
|
12
|
+
use_arguments(op) do |args|
|
13
|
+
opts = {}
|
14
|
+
if session = args.use('session')
|
15
|
+
opts[:session] = entities.get(:session, session)
|
16
|
+
end
|
17
|
+
database[args.use!('collection')].create(**opts)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def drop_collection(op)
|
22
|
+
database = entities.get(:database, op.use!('object'))
|
23
|
+
use_arguments(op) do |args|
|
24
|
+
collection = database[args.use!('collection')]
|
25
|
+
collection.drop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_collection_exists(op, state = true)
|
30
|
+
consume_test_runner(op)
|
31
|
+
use_arguments(op) do |args|
|
32
|
+
client = ClientRegistry.instance.global_client('authorized')
|
33
|
+
database = client.use(args.use!('databaseName')).database
|
34
|
+
collection_name = args.use!('collectionName')
|
35
|
+
if state
|
36
|
+
unless database.collection_names.include?(collection_name)
|
37
|
+
raise Error::ResultMismatch, "Expected collection #{collection_name} to exist, but it does not"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
if database.collection_names.include?(collection_name)
|
41
|
+
raise Error::ResultMismatch, "Expected collection #{collection_name} to not exist, but it does"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def assert_collection_not_exists(op)
|
48
|
+
assert_collection_exists(op, false)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_index(op)
|
52
|
+
collection = entities.get(:collection, op.use!('object'))
|
53
|
+
use_arguments(op) do |args|
|
54
|
+
opts = {}
|
55
|
+
if session = args.use('session')
|
56
|
+
opts[:session] = entities.get(:session, session)
|
57
|
+
end
|
58
|
+
|
59
|
+
collection.indexes.create_one(
|
60
|
+
args.use!('keys'),
|
61
|
+
name: args.use!('name'),
|
62
|
+
**opts,
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def assert_index_exists(op)
|
68
|
+
consume_test_runner(op)
|
69
|
+
use_arguments(op) do |args|
|
70
|
+
client = ClientRegistry.instance.global_client('authorized')
|
71
|
+
database = client.use(args.use!('databaseName'))
|
72
|
+
collection = database[args.use!('collectionName')]
|
73
|
+
index = collection.indexes.get(args.use!('indexName'))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_index_not_exists(op)
|
78
|
+
consume_test_runner(op)
|
79
|
+
use_arguments(op) do |args|
|
80
|
+
client = ClientRegistry.instance.global_client('authorized')
|
81
|
+
database = client.use(args.use!('databaseName'))
|
82
|
+
collection = database[args.use!('collectionName')]
|
83
|
+
begin
|
84
|
+
index = collection.indexes.get(args.use!('indexName'))
|
85
|
+
raise Error::ResultMismatch, "Index found"
|
86
|
+
rescue Mongo::Error::OperationFailure => e
|
87
|
+
if e.code == 26
|
88
|
+
# OK
|
89
|
+
else
|
90
|
+
raise
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Unified
|
2
|
+
class EntityMap
|
3
|
+
def initialize
|
4
|
+
@map = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def set(type, id, value)
|
8
|
+
@map[type] ||= {}
|
9
|
+
if @map[type][id]
|
10
|
+
raise Error::EntityMapOverwriteAttempt,
|
11
|
+
"Cannot set #{type} #{id} because it is already defined"
|
12
|
+
end
|
13
|
+
@map[type][id] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(type, id)
|
17
|
+
unless @map[type]
|
18
|
+
raise Error::EntityMissing, "There are no #{type} entities known"
|
19
|
+
end
|
20
|
+
unless v = @map[type][id]
|
21
|
+
raise Error::EntityMissing, "There is no #{type} #{id} known"
|
22
|
+
end
|
23
|
+
v
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_any(id)
|
27
|
+
@map.each do |type, sub|
|
28
|
+
if sub[id]
|
29
|
+
return sub[id]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
raise Error::EntityMissing, "There is no #{id} known"
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](type)
|
36
|
+
@map[type]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Unified
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
class ResultMismatch < Error
|
6
|
+
end
|
7
|
+
|
8
|
+
class ErrorMismatch < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class UnhandledField < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class EntityMapOverwriteAttempt < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class EntityMissing < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidTest < Error
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|