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.
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