mongo 2.14.1 → 2.15.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +4 -1
  4. data/Rakefile +8 -15
  5. data/lib/mongo/auth/aws/conversation.rb +1 -4
  6. data/lib/mongo/auth/base.rb +13 -7
  7. data/lib/mongo/auth/conversation_base.rb +32 -0
  8. data/lib/mongo/auth/cr/conversation.rb +6 -29
  9. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  10. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  11. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  12. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  13. data/lib/mongo/auth/user/view.rb +16 -9
  14. data/lib/mongo/auth/x509/conversation.rb +4 -25
  15. data/lib/mongo/bulk_write.rb +21 -18
  16. data/lib/mongo/client.rb +82 -6
  17. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  18. data/lib/mongo/cluster.rb +19 -2
  19. data/lib/mongo/collection/view/aggregation.rb +1 -1
  20. data/lib/mongo/collection/view/change_stream.rb +1 -1
  21. data/lib/mongo/collection/view/iterable.rb +7 -17
  22. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  23. data/lib/mongo/collection/view/readable.rb +42 -20
  24. data/lib/mongo/collection/view/writable.rb +14 -14
  25. data/lib/mongo/collection.rb +6 -6
  26. data/lib/mongo/cursor.rb +2 -12
  27. data/lib/mongo/database/view.rb +1 -1
  28. data/lib/mongo/database.rb +8 -3
  29. data/lib/mongo/error/bulk_write_error.rb +17 -3
  30. data/lib/mongo/error/internal_driver_error.rb +22 -0
  31. data/lib/mongo/error/operation_failure.rb +21 -2
  32. data/lib/mongo/error/parser.rb +65 -12
  33. data/lib/mongo/error/server_api_conflict.rb +23 -0
  34. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  35. data/lib/mongo/error/unmet_dependency.rb +21 -0
  36. data/lib/mongo/error.rb +9 -1
  37. data/lib/mongo/index/view.rb +21 -11
  38. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  39. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  40. data/lib/mongo/monitoring.rb +13 -4
  41. data/lib/mongo/operation/collections_info/command.rb +2 -2
  42. data/lib/mongo/operation/collections_info.rb +18 -1
  43. data/lib/mongo/operation/context.rb +99 -0
  44. data/lib/mongo/operation/indexes.rb +15 -1
  45. data/lib/mongo/operation/insert/command.rb +2 -2
  46. data/lib/mongo/operation/insert/legacy.rb +2 -2
  47. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  48. data/lib/mongo/operation/list_collections/result.rb +4 -1
  49. data/lib/mongo/operation/parallel_scan/command.rb +2 -1
  50. data/lib/mongo/operation/result.rb +2 -0
  51. data/lib/mongo/operation/shared/executable.rb +24 -14
  52. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  53. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  54. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  55. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  56. data/lib/mongo/operation/shared/read_preference_supported.rb +36 -38
  57. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  58. data/lib/mongo/operation/shared/sessions_supported.rb +15 -5
  59. data/lib/mongo/operation/shared/write.rb +8 -18
  60. data/lib/mongo/operation.rb +2 -2
  61. data/lib/mongo/protocol/compressed.rb +51 -5
  62. data/lib/mongo/protocol/message.rb +20 -2
  63. data/lib/mongo/protocol/msg.rb +38 -13
  64. data/lib/mongo/protocol/query.rb +11 -11
  65. data/lib/mongo/query_cache.rb +30 -0
  66. data/lib/mongo/retryable.rb +1 -1
  67. data/lib/mongo/server/app_metadata.rb +52 -18
  68. data/lib/mongo/server/connection.rb +5 -0
  69. data/lib/mongo/server/connection_base.rb +13 -10
  70. data/lib/mongo/server/connection_pool.rb +6 -2
  71. data/lib/mongo/server/description/features.rb +9 -8
  72. data/lib/mongo/server/description.rb +4 -0
  73. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  74. data/lib/mongo/server/monitor/connection.rb +9 -10
  75. data/lib/mongo/server/monitor.rb +20 -1
  76. data/lib/mongo/server/pending_connection.rb +24 -6
  77. data/lib/mongo/server/push_monitor.rb +11 -1
  78. data/lib/mongo/server.rb +7 -1
  79. data/lib/mongo/server_selector/secondary_preferred.rb +7 -2
  80. data/lib/mongo/session/session_pool.rb +4 -2
  81. data/lib/mongo/session.rb +2 -2
  82. data/lib/mongo/socket/ssl.rb +8 -0
  83. data/lib/mongo/socket.rb +29 -4
  84. data/lib/mongo/uri/options_mapper.rb +38 -0
  85. data/lib/mongo/utils.rb +15 -0
  86. data/lib/mongo/version.rb +1 -1
  87. data/lib/mongo.rb +23 -0
  88. data/spec/README.md +24 -1
  89. data/spec/integration/auth_spec.rb +25 -15
  90. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  91. data/spec/integration/change_stream_spec.rb +4 -4
  92. data/spec/integration/command_monitoring_spec.rb +2 -2
  93. data/spec/integration/connection_spec.rb +2 -0
  94. data/spec/integration/docs_examples_spec.rb +8 -1
  95. data/spec/integration/fork_reconnect_spec.rb +4 -1
  96. data/spec/integration/ocsp_verifier_spec.rb +13 -7
  97. data/spec/integration/operation_failure_code_spec.rb +1 -1
  98. data/spec/integration/operation_failure_message_spec.rb +90 -0
  99. data/spec/integration/query_cache_spec.rb +0 -45
  100. data/spec/integration/reconnect_spec.rb +1 -1
  101. data/spec/integration/snappy_compression_spec.rb +25 -0
  102. data/spec/integration/srv_monitoring_spec.rb +1 -1
  103. data/spec/integration/transactions_examples_spec.rb +6 -0
  104. data/spec/integration/zlib_compression_spec.rb +1 -1
  105. data/spec/integration/zstd_compression_spec.rb +26 -0
  106. data/spec/lite_spec_helper.rb +7 -1
  107. data/spec/mongo/address_spec.rb +15 -11
  108. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  109. data/spec/mongo/auth/ldap_spec.rb +5 -1
  110. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  111. data/spec/mongo/auth/scram_spec.rb +1 -1
  112. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  113. data/spec/mongo/client_construction_spec.rb +207 -33
  114. data/spec/mongo/client_spec.rb +17 -0
  115. data/spec/mongo/cluster_spec.rb +1 -0
  116. data/spec/mongo/collection/view/explainable_spec.rb +1 -1
  117. data/spec/mongo/collection/view/readable_spec.rb +33 -19
  118. data/spec/mongo/collection_crud_spec.rb +4357 -0
  119. data/spec/mongo/collection_ddl_spec.rb +534 -0
  120. data/spec/mongo/collection_spec.rb +5 -4859
  121. data/spec/mongo/database_spec.rb +66 -4
  122. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  123. data/spec/mongo/error/parser_spec.rb +37 -6
  124. data/spec/mongo/index/view_spec.rb +4 -0
  125. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  126. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  127. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  128. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  129. data/spec/mongo/operation/command_spec.rb +6 -3
  130. data/spec/mongo/operation/create_index_spec.rb +6 -3
  131. data/spec/mongo/operation/create_user_spec.rb +6 -3
  132. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  133. data/spec/mongo/operation/delete_spec.rb +11 -7
  134. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  135. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  136. data/spec/mongo/operation/get_more_spec.rb +3 -1
  137. data/spec/mongo/operation/indexes_spec.rb +5 -1
  138. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  139. data/spec/mongo/operation/insert_spec.rb +15 -12
  140. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  141. data/spec/mongo/operation/read_preference_legacy_spec.rb +19 -9
  142. data/spec/mongo/operation/read_preference_op_msg_spec.rb +3 -3
  143. data/spec/mongo/operation/remove_user_spec.rb +6 -3
  144. data/spec/mongo/operation/result_spec.rb +1 -1
  145. data/spec/mongo/operation/update/bulk_spec.rb +9 -6
  146. data/spec/mongo/operation/update_spec.rb +10 -7
  147. data/spec/mongo/operation/update_user_spec.rb +4 -1
  148. data/spec/mongo/protocol/compressed_spec.rb +26 -12
  149. data/spec/mongo/query_cache_middleware_spec.rb +55 -0
  150. data/spec/mongo/retryable_spec.rb +3 -2
  151. data/spec/mongo/server/app_metadata_shared.rb +7 -33
  152. data/spec/mongo/server/app_metadata_spec.rb +2 -0
  153. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  154. data/spec/mongo/server/connection_pool_spec.rb +1 -1
  155. data/spec/mongo/server/connection_spec.rb +24 -17
  156. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  157. data/spec/mongo/server/monitor_spec.rb +9 -1
  158. data/spec/mongo/server_selector/secondary_preferred_spec.rb +6 -6
  159. data/spec/mongo/server_spec.rb +15 -2
  160. data/spec/mongo/socket/ssl_spec.rb +40 -0
  161. data/spec/mongo/socket_spec.rb +2 -2
  162. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  163. data/spec/runners/connection_string.rb +0 -4
  164. data/spec/runners/crud/requirement.rb +40 -3
  165. data/spec/runners/crud/verifier.rb +8 -0
  166. data/spec/runners/transactions/operation.rb +1 -1
  167. data/spec/runners/transactions/test.rb +1 -0
  168. data/spec/runners/unified/assertions.rb +249 -0
  169. data/spec/runners/unified/change_stream_operations.rb +26 -0
  170. data/spec/runners/unified/crud_operations.rb +199 -0
  171. data/spec/runners/unified/ddl_operations.rb +96 -0
  172. data/spec/runners/unified/entity_map.rb +39 -0
  173. data/spec/runners/unified/error.rb +25 -0
  174. data/spec/runners/unified/event_subscriber.rb +91 -0
  175. data/spec/runners/unified/exceptions.rb +21 -0
  176. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  177. data/spec/runners/unified/support_operations.rb +250 -0
  178. data/spec/runners/unified/test.rb +393 -0
  179. data/spec/runners/unified/test_group.rb +28 -0
  180. data/spec/runners/unified/using_hash.rb +31 -0
  181. data/spec/runners/unified.rb +96 -0
  182. data/spec/shared/lib/mrss/cluster_config.rb +0 -3
  183. data/spec/shared/lib/mrss/docker_runner.rb +0 -3
  184. data/spec/shared/lib/mrss/lite_constraints.rb +0 -16
  185. data/spec/shared/lib/mrss/server_version_registry.rb +0 -3
  186. data/spec/shared/lib/mrss/spec_organizer.rb +0 -3
  187. data/spec/shared/shlib/server.sh +1 -1
  188. data/spec/spec_helper.rb +4 -1
  189. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  190. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
  191. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  192. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  193. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  194. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  195. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  196. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  197. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  198. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  199. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  200. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  201. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  202. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  203. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  204. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  205. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  206. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  207. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  208. data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
  209. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  210. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  211. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  212. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  213. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  214. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  215. data/spec/spec_tests/unified_spec.rb +15 -0
  216. data/spec/spec_tests/uri_options_spec.rb +16 -0
  217. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  218. data/spec/support/client_registry.rb +4 -8
  219. data/spec/support/client_registry_macros.rb +4 -4
  220. data/spec/support/common_shortcuts.rb +15 -1
  221. data/spec/support/shared/session.rb +2 -2
  222. data/spec/support/spec_config.rb +42 -11
  223. data/spec/support/utils.rb +64 -3
  224. data.tar.gz.sig +0 -0
  225. metadata +1005 -915
  226. metadata.gz.sig +0 -0
  227. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
  228. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  229. data/spec/integration/secondary_reads_spec.rb +0 -102
  230. 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